• static 멤버 함수

멤버 함수를 static으로 선언이 가능하다.

 

static 멤버 함수는 어떤 특정 객체와도 결합되지 않기 때문에

 

1. 객체에 의해 호출될 필요가 없고, 객체에 의해 호출될 수 없다.

※static멤버변수는 객체에 의해 호출될 수 있다.

그렇기에 this포인터를 가지지 않는다.

 

2. 어떤 객체와도 결합되지 않기에 static 멤버 함수 내에서 사용가능한 멤버는 static 멤버 변수 밖에 없다.

 

3. 접근 지정자에 영향을 받는다.(public만 자유롭게 사용가능)

 

이러한 특징을 지니고 있기에 클래스의 설정 값, 플래그 같은 걸 설정하기에 자주 사용했다.

 


  • 생성자에 new를 사용할 때 주의 사항

생성자에 new를 사용하여 동적할당 받았다면 소멸자에 반드시 delete로 해제 해주자.

 

new와 delete의 사용은 서로 어울려야 한다.

new는 delete와, new [ ]는 delete[ ] 처럼.

 

생성자가 여러개일 경우에는 new면 모두 new, new [ ]면 모두 new [ ] 로 사용해야한다.

이유는 소멸자가 한개이기 때문에 delete도 한번만 사용할 수 있기 때문이다.

 

깊은 복사가 필요한 클래스라면,

깊은 복사에 영향이 가는 static 멤버들도 갱신을 해주자

 


  • 객체 참조형 리턴시 주의

리턴되는 객체가 피호출 함수에 지역적이라면 참조로 리턴하면 안된다.

참조도 내부적으로 포인터로 작동하기 때문에 함수가 끝나면 쓰레기 값을 참조하게 된다.

절대 하지말자~~~

 


  • 객체와 함께 사용하는 위치지정new

객체와 함께 사용하는 위치지정new를 사용하는 경우 주의할 것이 있다.

 

먼저 생성부분이다.

pb와 pc는 buffer에 각 생성되었다.

하지만 생성되는 위치가 겹쳐 pb가 pc에 묻혀서 데이터가 유실될것이다.

그래서 이렇게 바꿔준다.

 

그리고 메모리 해제 부분인데

저 buffer는 delete[ ]로 해제가 가능하다.

그럼 buffer가 해제 될때 그 안에 있던 pb, pc도 메모리 해제가 될까? 한다면

메모리 해제부분에서는 어차피 buffer라는 하나의 메모리로 할당받았으니

buffer라는 메모리만 돌려준다면 메모리상에서는 이상할게 없다.

 

하지만 객체의 소멸자가 호출되는가의 문제가 생긴다.

그래서 buffer가 해제 되기 전에 안에 있던 객체의 소멸자를 모두 호출해준다.

이렇게 실행하면 오류가 난다.

pa는 기본new로 할당받았으니 delete로 해제하여도 문제가 없고

원인은 pb와 pc의 해제 방법이 잘못됐기 때문.

 

이 delete는 new와 연계되지만 위치지정new와는 연계되지 않는다.

그래서 직접 소멸자를 호출해주어야 한다.

 


  • 특별 멤버 함수 (Special member function)

특정 경우에 컴파일러가 자동으로 생성하는 클래스의 멤버 함수이다.

 

기본 생성자

더보기

값을 초기화하고 싶지 않을 때 편리하며 객체들의 배열을 만들 때는 무조건 사용한다.

혹 매개변수가 있는 생성자도 모든 매개변수에 디폴트 값을 제공한다면 기본 생성자가 될 수 있다.

단, 기본 생성자는 하나만 가질 수 있기 때문에 이러한 코드는 모호하여 불가능하다.

복사 생성자

더보기

 

디폴트 복사 생성자는 static멤버를 제외한 멤버들을 각 값으로 복사한다.(얕은 복사)

디폴트 복사 생성자: 사용자가 정의하지 않아서 C++가 자동으로 만든 복사 생성자

 

새로운 객체가 생성되어 같은 클래스의 객체로 초기화될 때마다 호출된다.

초기화될 때: 초기화는 새 개체를 선언하거나, 함수 인수를 값으로 전달하거나, 함수에서 값으로 반환할 때 발생합니다.

임시 객체를 생성할 때마다 복사 생성자를 사용한다.???

어셈블리어로 확인하면

왼쪽의 call MyClass::MyClass(078119Ah)로 가보면 오른쪽의 코드로 진행된다. 즉, 모두 복사 연산자가 호출 된다.

 

MyClass d = MyClass(a); 같은 코드도

MyClass b(a)와 내부 동작이 완전히 같게 복사 생성자로 바뀌어 진행된다.

 

근데 공부하면서 헷갈렸었던 주의할 것이 있는데 

MyClass d = MyClass(a) 부분처럼 대입 연산자(=)가 사용되었을 때

처리를 두 가지 방법으로 생각해 볼 수 있다.

 

1. 위의 어셈블리처럼 복사 생성자를 직접 사용하는 것

ex) "MyClass d = MyClass(a)"  =>  "MyClass d(a)"

 

2. MyClass(a)부분에서 임시 객체를 생성하고 그 임시 객체를 대입 연산자로 d에 대입하는 두 단계로 나누어 처리

ex) MyClass d = 임시 객체

 

어떠한 방법을 따르는지는 컴파일러에게 달렸지만 복사 생성자는 항상 호출되고 대입 연산자가 있을 시에

대입 연산자가 호출될 수도 있다.

복사 할당 연산자(복사 대입 연산자)

더보기
이런 형식이다.

위에서 객체 = 객체처럼 자연스럽게 대입 연산자를 사용했지만

이것 또한 우리가 정의하지 않아도 사용할 수 있었다, 그 이유는 C++에서 자동으로 만들어주었기 때문이다.

 

ms문서에 보면 초기화와 할당이라는 단어를 제일 먼저 설명해준다.

ms문서 : https://docs.microsoft.com/ko-kr/cpp/cpp/copy-constructors-and-copy-assignment-operators-cpp?view=msvc-170

그 이유는 초기화가 일어날 때는 복사 생성자, 할당이 일어날 때는 복사 할당 연산자가 실행되기 때문이다.

초기화: 초기화는 새 개체를 선언하거나, 함수 인수를 값으로 전달하거나, 함수에서 값으로 반환할 때 발생합니다.

할당: 한 개체의 값이 다른 개체에 할당되면(a = b) 첫 번째 개체(b)가 두 번째 개체(a)에 복사됩니다.

 

이렇게 초기화, 할당이라는 조건으로 나뉘어 실행되는 이유는 내가 생각하기에

새로 객체가 생성되지 않는 경우의 복사 상황에는 복사 생성자가 실행되지 않기 때문이라고 생각한다.

그래서 따로 처리가 필요한 것이고 그 처리가 복사 할당 연산자가 된 것이다.

 

초기화가 아닌 할당이 조건이라는 건,

복사받을 객체가 이미 초기화가 되어있고 어떤 값이 들어있다는 뜻이다.

 

이는 즉, 초기화(복사 생성자)와 달리

깊은 복사가 필요할 때 이전에 있던 동적 할당된 메모리를 필수로 해제시켜주어야 한다는 의미도 있다.

 

그리고 또 하나의 차이점으로 호출한 객체에 대한 참조를 리턴한다

(생성자는 리턴이 없음)

좀 더 쉽게 풀면 하나의 객체를 기존의 다른 객체에 복사할 때 사용된다고 볼 수 있다.

 

이 복사 생성자와 복사 대입 연산자 중 하나라도 정의한다면

나머지 하나를 정의해주는 게 좋다.

그래야 모든 복사 상황에 대해 정의가 되니 보다 코드의 의미가 명확해진다.

이동 생성자

이동 할당 연산자(이동 대입 연산자)

주소 연산자

기본 소멸자

 

모두 각각 정의되지 않았을 경우 자동으로 생성해 준다.

이동 관련은 뒤에 정리


  • 얕은 복사, 깊은 복사

new에 의해 초기화되는 포인터들을 멤버로 가지고 있을 경우에

포인터 자체를 복사하는 것이 아니라, 그 포인터가 지시하는 데이터를 복사하는 복사 생성자를 정의해주면

그것을 깊은 복사라 한다.

 

얕은 복사와 깊은 복사라는 이름이 지어지게 된 이유는

얕은 복사는 포인터가 지시하는 데이터를 복사하기 위해 깊게 파고들지 않고, 그 주소만 얕게 복사하기 때문에

지어진 이름이다.

 

문제로 얕은 복사 시에는 하나의 할당된 메모리를 두 개의 객체가 멤버 변수로 접근하기에

하나의 객체가 소멸하여 소멸자를 호출하여 메모리가 해제될 때

다른 하나의 객체는 원하지 않아도 메모리가 해제된 포인터 멤버 변수를 가지게 되어 큰 문제가 될 수 있다.

 

그래서 깊은 복사를 해주는 것

 


  • 복사 생성자 추가

위의 코드에서 String클래스와 string클래스는 생성자에서 내부적으로 동적할당이 일어난다.

그렇다면 Magazine클래스에 대한 복사가 일어날때 복사 생성자나 복사 할당 연산자를 정의해야하는가?

 

한다면 여기서는 아니다.

 

이유는 Magazine이 복사가 일어나면 디볼트 복사 생성자나 디폴트 복사 할당 연산자는

해당 멤버 자료형의 복사 생성자와 복사 할당 연산자를 사용하기 때문이다.

 

그렇기 때문에 String,string이 각각 복사 관련 함수를 깊은 복사로 잘 정의 했다면 정의한 복사 함수들이

실행되는것이다.

 

근데 같은 클래스가 아닌 다른 클래스에 대한 복사면 예외가 있다고 한다.

뒤에서 나온단다

  • static 멤버 변수

멤버 변수는 객체마다 존재하며 각각 다른 값을 가질수 있는 반면에

static 멤버 변수는 하나의 데이터로 같은 클래스의 객체가 공유하는 멤버 변수이다.

 

클래스 내부에 static키워드를 추가하여 멤버 변수를 선언하며

어떠한 스코프 안에 선언된 단순 정적지역변수와 같은 점도 있고 차이도 있다.

 

1. 차이점으로 먼저 클래스 선언부 안에서 초기화 할 수 없다.

 

이유는 static 멤버 변수는 객체의 일부분으로 저장되는 것이 아니라 별도로 저장되기 때문이다.

그렇기 때문에 초기화 방법이 다른것이다.

 

기본 멤버 변수는 객체가 생성될 때 메모리에 올라가며

각 객체마다 다른 값을 가질 수가 있다.

그렇기에 선언부나 생성자에서 계속 초기화가 이루어져도 상관 없지만

 

static멤버 변수는 초기화될 때 메모리에 올라가게 된다.

초기화가 여러번 되면 매우 혼란스러워지기 때문에

오직 한번만 가능하다.

그렇기에 선언부 외부에 한번 초기화가 가능한것이다.

왼) 초기화부분을 주석 처리 했을 때 메모리에서 찾을 수 없다 // 오) 주석 해제 하면 메모리에 올라가 있는 걸 확인할 수 있다.

 

단 예외로 const를 붙이면 선언부에서 초기화 가능하다.

값이 변하지 않기 때문에 상관없다는 뜻인가?

 

2. 차이점으로 두가지 접근방법이 있다.

 

객체를 통한 방법, 범위 결정 연산자를 통한 접근

 

4. 같은점으로 접근 지정자에 영향을 받는다.

 

public일때만 외부에서 자유롭게 사용할 수 있다.

protected나 private이면 멤버함수, 정적멤버함수, 프렌드함수 로 접근할 수 있다.

 

 

추가로 이 초기화는 헤더파일이 아닌 cpp파일에 넣는다.

프로그램은 헤더 파일을 여러 파일에 포함시킬수 있기 때문에 초기화 구문이 여러 벌 만들어 지는 셈이 되므로

에러가 발생하기 때문이다.


  • 추가

기본 멤버변수가 생성자가 아닌 선언과 동시에 초기화도 가능한 이유? (in-class 초기화)

더보기

예전 C++에서는 이러한 문법이 허용되지 않았다.

그래서 오직 생성자에서만 초기화가 가능했다.

 

하지만 멤버변수 한두 개를 초기화하려고 생성자를 쓰는 게 귀찮았는지

저런 문법이 가능토록 변경되었다고 한다.

 

그 문법을 in class 초기화라 한다

in class 초기화는 

 

in class초기화를 하게되면 멤버이니셜라이저를 사용하는 것과 동일하다.

 

 

 

TODO::차이점 서술하기 

1.하지만 멤버이니셜라이저를 사용하는 모든 생성자는 그에 상응하는 in-class 초기화를 무효화한다.

2.밑에 링크 이해 하기

 

https://blog.naver.com/tipsware/221792822911

 

[Q&A] 멤버 변수의 초기화 방법

: C++ 언어 관련 전체 목차 - http://blog.naver.com/tipsware/221028559903 : C++ 언어 관련 Q&A ...

blog.naver.com

https://stackoverflow.com/questions/27352021/c11-member-initializer-list-vs-in-class-initializer

 

C++11 member initializer list vs in-class initializer?

What difference between these ways of initializing object member variables in C++11 ? Is there another way ? which way is better (performance) ?: class any { public: obj s = obj("value"); ...

stackoverflow.com

 

  • 하나의 매개변수를 취하는 생성자

오 이건 처음 알게 된 내용이다.

 

하나의 매개변수를 취하는 생성자는 사용자 데이터형으로 캐스팅될 때도 사용이 된다.

처음보면 A = 44가 말도 안되었지만 가능이였다ㅋㅋ
실행결과

 

A = 44; 구문에서

c++컴파일러는 먼저 type casting이 가능한지 확인한다.
찾을 때 매개변수가 하나인 생성자도 같이 찾아보기 때문에 이에 맞는 생성자가 있다면 호출되는 것.
A = MyClass(44)로 변환된다는 말.
고로 생성자에 44를 넣은 MyClass클래스의 임시 객체를 생성하여 대입한다.

 

이렇게 컴파일러에서 매개변수가 한 개인 생성자가 사용되는 때는 여러 가지가 있는데

방금 위의 코드로 예시를 들자면

 

1. 객체에 매개변수형 값으로 초기화할 때

2. 객체에 매개변수형 값을 대입할 때

3. 객체 타입의 자료형인 매개변수를 기대하는 함수에 생성자 매개변수 자료형으로 매개변수를 전달할 때

4. 객체타입의 값을 리턴하도록 선언된 함수가 생성자 매개변수의 자료형 값으로 리턴하려 할 때

5. 1~4 상황에서 생성자 매개변수의 자료형으로 변환할 수 있는 내장 데이터형을 사용할 때

말 그대로

생성자 매개변수의 자료형(여기서는 int)로 변환할 수 있는 내장 데이터형(flaot, double...)을 사용한 것뿐

진행순서는 들어온 실수 자료형을 int형으로 변환 후 다시 int형을 MyClass형으로 변경한다.

 

근데 이처럼 자동으로 형 변환을 해주는 기능을 막는 기능 또한 있다.

바로 explicit이다.

 


  • explicit

explicit 키워드를 사용하면 명시적으로만 사용 가능하다

즉 암시적 데이터형 변환을 못하게 막는다.

5가지 상황 중 가능했던 상황도 막을 수 있다.


  • 변환 함수 (conversion function)

위에서 어떠한 수를 객체로 타입 캐스팅했었는데

그 반대로도 가능하다.

 

위처럼 생성자가 아닌 변환 함수를 이용하는 것인데, 사용자 정의 강제 데이터형 변환이다.

어떠한 typeName으로 변환하려면 다음과 같은 형식으로 사용한다.

 

operator typeName( );

 

이처럼 변환 함수는 리턴타입, 매개변수를 가질 수 없게 만들어져 있다.

 

예시 코드이다.

실행결과

다만 이처럼 여러 개의 변환 함수가 있을 때

모호하지 말고 명확하게 타입을 써주자

 

근데 변환 함수가 한 개라면 어떤 걸로 변환될지 정해져 있기에 명확하지 않은 암시적으로도 가능하다.

근데 또 사람이 실수할 수도 있기 때문에 C++11에서 explicit과 함께 사용이 가능토록 변경되었다.

 


  • 변환과 프렌드

변환함수와 프렌드를 같이 사용할 경우 이러한 혼동이 올 수 있다.

  • 프렌드

클래스에서 다른 클래스의 private영역에 접근할 수 있는 방법은 보통 public 메서드들을 이용하여 접근한다.

그 외로 다른 방법을 하나 제공하는데 그것이 프렌드이다.

 

프렌드 멤버 함수

프렌드 함수

프렌드 클래스

 

정도로 분류할 수 있다.


  • 프렌드 멤버 함수

프렌드 멤버 함수의 필요성은 operator 오버로딩으로 예를 많이 든다.

첫 번째의 "cout << A + 4"같은 경우는

피연산자가 어떠한 클래스에 속한다는 것을 컴파일러가 인식하면,

그 연산자를 해당하는 연산자 함수로 대체하여 실행한다

 

두 번째의 "cout << 4 + A"같은 경우는

컴파일러가 +연산자를 수행할 때 피연산자의 자료형을 찾아서 그에 맞는 오버 로딩된 +연산자를 수행할 텐데

위 수식과 동일한 오버로딩된 함수가 없어서 에러가 난다.

 

만들고 싶어도 이항 연산자 함수의 매개변수는 1개로 정해져 있으며

이 매개변수도 연산자 뒤에 오는 피연산자로 들어오게끔 정해져 있다.

 

이 두 번째를 해결할 때 프렌드 멤버 함수가 좋은 선택지가 된다.

 

생성은 아주 쉽다.

앞에 함수 원형 앞에 friend만 붙여주면 된다.

이 프렌드 멤버 함수는 두 가지 의미를 가지고 있다.

 

1. 클래스 안에 선언되었지만 정확히 멤버함수는 아니다. => 멤버연산자를 사용하여 호출되지 않는다.

멤버함수는 아니다 : 그래서 클래스 외부에 정의가 될때 제한자가 빠진걸 볼수 있다.

2. 멤버함수는 아니라고 했지만 멤버함수와 동등한 접근권한을 가진다.

동등한 접근권한 : protected, private에 관계없이 접근 가능하다는 말

 

이렇게 생성된 프렌드 멤버 함수를 사용하면 된다.

 


  • 추가 내용

내가 friend 멤버 함수 코드를 작성할 때 밑처럼 항상 클래스 선언부에 정의까지 작성하긴 했는데

이렇게 보면 operator+(int, MyClass) 함수가 MyClass에 속해있다고 생각이 들겠지만

사실 외부의 함수를 MyClass에서 friend화 한다고 생각하는 게 다른 friend종류를 이해하기에 편하고 맞는 거 같다.

1
2

그렇기 때문에 저 operator+(int, MyClass)가 MyClass의 멤버함수로가 아닌 전역적으로 사용될 수 있는 것이다.

 

 

 

  • 연산자 오버로딩

정의 되지않은 데이터끼리의 연산을 직접 정의하여 기호만으로도 그 정의대로 연산이 실행되게 할 수 있다

오버로딩은 다형성의 대표적인 예임

 

예를들어 Vector class가 있다고 생각하면 컴퓨터는 Vector + Vector이 어떻게 연산해야하는지 모른다

그래서 정의를 해주어 그대로 연산되게 하는것

 

 

연산자를 오버로딩 하려면 연산자 함수라는 특별한 함수를 사용한다

operator op ( argument-list )

 

이  op는 연산자 기호를 나타낸다 ( + , - , * , / , [ ])

 

이 연산자 함수는 함수표기와 연산자표기가 모두 가능하다

 

밑은 오버로딩 선언 및 정의이다

여기서 처음 알게된건 arg.x처럼 다른 객체의 private멤버 함수여도

같은 클래스로 생성되었다면 private에 선언된 데이터를 사용가능하다는 것이다

 

그리고 +연산자는 두개의 피연산자를 요구하는데, this 포인터를 통하여 암시적으로 하나가 전달된다.

다른하나는 매개변수로 명시적 전달이 된다.

 

연산자의 왼쪽에 있는 객체가 호출한 객체이고 오른쪽이 매개변수로 전달되는 객체이다

 

세개 이상의 객체의 연속된 연산도 가능하다

덧셈 연산자는 왼쪽에서 오른쪽으로 결합하는 연산자이기에 

a + b 가 실행되고  (a + b의결과) + c 가 실행된다

 

이러한 연산자 오버로딩은 편하지만 기호로만 어떤 기능인지 알수있기때문에

 

정의가 명확해야한다.

 

이건 설계시에도 마찬가지긴 하다


  • 연산자 오버로딩 제약

제한되는 상황이 있는데

 

최소 하나 이상의 피연산자가 사용자 정의 데이터형 이어야한다

[ float - float 같은 모두 표준 데이터형의 연산은 오버로딩 할 수 없다 ]

 

기호를 새로 만들 수 없다

[ operator *+-( ) 처럼ㅋㅋ ]

 

오버로딩 불가능한 기호들이다

 

  • 추상화 데이터타입(abstract dadta type = ADT)

 

int, double 이런 데이터형이 아닌

사용자 정의 데이터형과 같은 느낌의 데이터형으로

 

자료구조와 많이 연관된 개념인듯하다

이러한 클래스도 추상자료형이라고 생각한다

그냥 클래스 선언부인데? 라는 생각이 들었고 이 때문에 죤내 헷갈렸는데

 

내가 생각으로는 멤버함수가 정의되지 않는다는게 중요한거 같다

 

아직 크게 감이 안오는데 나중에 다시 확인해야겠다

 

  • 객체배열의 초기화

 

초기화하는 생성자는 당연히 다른걸 써도 된다

초기화를 하지 않는 인덱스는 디폴트생성자가 실행되겠다

 


  • 클래스 사용 범위 상수

클래스 사용 범위 = 클래스 밖이 아닌 안에서 정의되는 이름에 적용

 

위의 코드에서 배열생성에 오류가 났다

이유는 a의 값을 알지 못해서인데

 

클래스선언은 클래스가 어떻게 생겼는지 서술하는것이지 객체가 생성되는건 아니다

= 객체가 생성되는게 아니니 값을 저장 할 기억 공간이 없다

= array 선언부에 a의 값은 모른다

 

그렇기에 클래스 선언부 내부에 상수를 사용하려면 두가지 방법이 있다

 

enum을 사용하는 방법

 enum은 정의만으로 메모리할당이 되지 않으며

[ 그래서 객체에 데이터 멤버를 생성하지 않는다 ]

define처럼 치환되는 방식이므로

객체가 생성되지 않아도 사용이 가능하다

 

 

static을 사용하는 방법

 

클래스 선언부 내부에 static을 사용하면 전역메모리에 올라가며 같은 클래스의 객체가 공유하고 사용 가능한

정적 멤버 변수가 된다

[정적 멤버 변수는 뒤에 자세히 나온다]

 

마찬가지로 객체의 생성과는 상관이 없기에 가능한 부분이다

 


  • enum class

C++11 에서 열거자에게 클래스 범위를 갖게하는 새로운 열거자 형식을 제공하였다

모두 enum class

열거자가 클래스 범위를 가지고 있기 때문에 저렇게 다른 enum정의의 식별자와 이름이 충돌할 가능성을 없앴다

 

단 그렇기 때문에 기존에 그냥 사용했던것과 다르게 구별할 문법이 필요한데 enumName::을 사용하면 된다

위에서 (int)로 형변환을 해준 이유는 enumClass에서 변수형 관리가 강화되었기 때문이다

기존enum처럼 일부상황에서 자동으로int형으로 암시적 전환이 이루어지지 않는다

+ Recent posts