Class 내부에서 this 포인터는 현재 객체를 가리키는 포인터이다.



this->멤버 변수명 을 통해 멤버 변수를 가리키나, 일반적으로 this->는 생략할 수 있다.


Line 6, 7: 멤버 변수명과 인자 이름이 같은 경우는 this->변수명 으로 접근해야 모호하지 않다.


Line 20: this를 리턴하거나 *this를 리턴하여 함수를 연속적으로 실행할 수 있다.


C++에서 클래스의 멤버 함수들을 C에서 구현한다면,

C의 struct에 데이터를 넣고 함수들은 모두 첫번째 인자로 this 포인터를 받는 것으로 생각할 수 있다.


이 경우 private, protected, public 구분이 안 되는 것 말고는 비슷하게 쓸 수 있다.



1. 상수 멤버 함수는 멤버 변수를 변경시키지 않는 멤버 함수를 나타내는 것으로,


(반환형) (함수이름)(함수인자) const { 구현 } 이런 식으로 인자와 구현 사이에 const 키워드를 넣는다.




2. 상수 함수가 필요한 이유


상수 객체는 상수 함수만 부를 수 있기 때문이다.



만일 Line 8에서 print() 함수에 const 키워드가 없다면 "error: passing 'const Point' as 'this' argument discards qualifiers [-fpermissive]" 이란 에러가 나게 된다.

이는 미리 컴파일 된 소스에 헤더만 가지고 가져다 쓸 경우, 멤버 함수 선언부에 const 가 없다면 컴파일러가 체크할 수 없기 때문이다.



3. 변수를 함수 인자로 전달할 때, 일반 변수로 전달한다면 앞에서 본 대로 복사생성자가 불리게 된다.


클래스의 경우는 성능 저하가 크게 되므로, 값을 변화시키지 않고 전달하려면 const 참조를 통해 전달하는 것이 일반적이다.


이때 const 참조로 가져온 변수는 멤버 함수 중에 상수 멤버 함수만 실행할 수 있고, 

따라서 성능 향상을 위해 const 참조로 인자를 전달하기 위해선 멤버값을 변화시키지 않는 함수는 상수 멤버 함수로 구현해야 한다.




4. 상수 멤버 함수에서는 모든 것이 const 취급이기에, 참조 return도 되지 않고 const 참조만 리턴할 수 있다.


5. 이름이 같은 함수를 const 버전과 일반 버전 둘 모두 제공할 수 있다.


이때 객체가 상수면 상수 멤버함수를, 상수가 아니면 일반 함수를 부르게 된다.


이 경우는 컴파일러에서 이름을 다르게 붙이는 오버로딩과 달리 이름도 같게 된다.




'프로그래밍 > C++' 카테고리의 다른 글

연산자 재정의 (Operator overloading)  (0) 2018.07.01
this 포인터  (0) 2017.11.13
정적 멤버 함수 (Static member function)  (0) 2017.11.13
정적 멤버 변수 (Static member variable)  (0) 2017.11.13
복사 생성자  (0) 2017.11.09


1. 정적 멤버 함수는 객체를 생성하지 않아도 존재하는 함수다.


2. 정적 멤버 함수는 정적 멤버 변수만 접근 가능하다.


이는 논리적으로 당연한 것이, 1에서 객체 생성 이전에 사용 가능한 함수라고 했으므로


멤버 변수에 접근할 수 있다면 존재하지 않는 것에 접근한다는 이야기가 된다.


3. 정적 멤버 함수는 


클래스이름::함수이름 또는 멤버변수이름.함수이름으로 접근 가능하나 가독성 측면에서 전자가 권장된다.


4. 선언부와 구현부를 나눈다면, static 키워드는 선언부에만 넣고 구현부에는 넣지 않는다.


만일 구현부에 static 키워드를 넣는다면, C의 static 키워드가 되어버린다.

'프로그래밍 > C++' 카테고리의 다른 글

this 포인터  (0) 2017.11.13
상수 멤버 함수 (const member function)  (0) 2017.11.13
정적 멤버 변수 (Static member variable)  (0) 2017.11.13
복사 생성자  (0) 2017.11.09
클래스 초기화 방법 정리  (0) 2017.11.08


1. 정적 멤버 변수는 클래스의 모든 객체가 공유하는 변수이다.


위 객체가 생성된 횟수를 count하기 위한 정적 멤버 변수 num을 정의하면 모든 객체가 공유하는 변수를 만들 수 있다.


임의의 전역 변수를 사용할 때보다 가독성이 좋고, 만일 정적 멤버 변수를 private에 놓는다면 클래스 외부에서 접근을 막을 수 있다.


모든 객체가 공유하기에 객체 생성 전에도 존재한다.


2. 정적 멤버 변수를 사용하기 위해선 위의 Line 18서처럼 반드시 클래스 외부에서 선언을 해야 한다.

(C++11부터 멤버 변수는 선언문에서 바로 초기화가 가능하나, 정적 멤버 변수는 불가능하다)


이때 선언부와 구현부를 나눈다면, 외부선언은 구현 파일에 작성한다.


단, static const 변수는 Line 8처럼 클래스 내부에서 바로 초기화하는 것이 가능하다.


3. 정적 멤버 변수는 

(클래스명)::정적변수명 또는

(멤버변수).정적변수명 으로 접근 가능하다.


단 가독성을 위해 전자의 방법이 권장된다.



1. 복사 생성자는 다른 개체를 인자로 받는 생성자이다.



여기서 c2는 따로 정의한 생성자 C(int x, int y) 와 달리 인자로 클래스 C의 객체를 받았는데 c1과 동일한 객체가 생성되었다.


이는 복사 생성자가 불린 것으로, 따로 사용자가 제공하지 않으면 컴파일러가 기본적으로 멤버 변수를 그대로 복사하는 복사 생성자를 제공해주기 때문이다.


복사 생성자를 정의할 때에는 아래처럼 한다. 인자를 const C&이 아니라 C 타입으로 받을 경우 에러가 발생하는데, 이유는 아래에서 알 수 있다.




2. 복사 생성자가 호출되는 경우


복사 생성자는 위에서처럼 동일 클래스 객체로 초기화 될 때,

함수에 인자로 call-by-value로 전달할 때,

함수에서 리턴할 때 불리게 된다.


위에서 복사 생성자를 정의하면서 인자로 call-by-value로 넘기게 될 경우 아직 정의 하지 않은 복사 생성자를 사용하기에 에러가 발생한다.


함수 인자로 call-by-value로 전달하게 되면 매번 복사생성자가 호출되기에 오버헤드가 발생하게 된다.

따라서 일반적인 경우 참조로 전달하게 된다.


리턴할 때 값을 리턴하게 되면, 복사 생성자가 호출되게 된다.



따라서 C 객체를 리턴할 때 위처럼 구현하게 되면 복사 생성자가 두번 불리게 된다.

이를 막기 위해 아래처럼 임시 객체를 리턴하게 되면 복사 생성자 호출 횟수를 줄일 수 있는데, 이를 Return Value Optimization (RVO) 라 한다.

단 컴파일러가 위의 함수도 자동으로 복사 생성자를 한번만 불리게 바꿔주는데, 이를 Named Return Value Optimization (NRVO)라 한다.


아니면 참조를 리턴할 경우 복사생성자 호출을 막을 수 있다. 

단, 이 경우에는 지역 변수의 참조를 리턴하지 않게 주의한다. (지역 변수는 함수가 끝나면서 파괴된다)



3. 멤버 변수가 포인터인 경우 디폴트 복사 생성자


멤버 변수가 포인터인 경우 컴파일러가 만든 복사 생성자는 포인터 값을 그대로 카피하기에 결과적으로 얕은 복사가 일어나게 된다.



위의 코드에서는 twin이 p를 인자로 받는 디폴트 복사 생성자를 통해 복사 되게 되는데, twin이 있는 bracket이 끝나는 시점에 twin의 소멸자가 불리게 되고 동시에 p.name이 가리키는 자원도 delete 되어 p의 데이터는 더이상 쓸모 없게 된다.


얕은 복사가 일어나면 안 되는 경우에는 다음과 같은 해결책을 사용해야 한다.


1) 깊은 복사

깊은 복사를 통해 Person을 구현한다. 이때 디폴트 복사 생성자와 대입 연산자를 구현해주면 된다.



2) 참조 계수

얕은 복사를 하되, 현재 name을 가리키고 있는 포인터의 수를 기록한다. 그리고 소멸자에서는 포인터를 아무도 가리키지 않을 때만 자원을 파괴한다.



깊은 복사를 이용하면 구현은 단순하지만 같은 데이터를 가진 자원이 중복되어서 존재하는 경우는 메모리 낭비가 많이 존재하게 된다.

반면 참조 계수는 같은 데이터는 한 자원만 존재하여 메모리 최적화가 되지만, 구현이 복잡하여 잘 만들어야 한다. 위의 구현에서는 p.name을 수정할 경우에 대한 구현이 되어있지 않다.


3) 복사 생성자의 제거


아래처럼 복사 생성자를 제거해버리는 방법도 있다.



복사 생성자를 private에 놓는 경우 복사 생성하는 부분에서 "링커 에러"를 발생한다.

C++11에서 추가된 delete 구문으로 복사 생성자를 삭제하는 경우 "컴파일 에러"가 발생한다.


4. 소유권 이전


move 생성자를 이용하는 방법.

클래스를 초기화하는 방법은 다음의 세 개가 있다.


1. 초기화 리스트

생성자의 초기화 리스트에서 다른 생성자를 부르는 것을 위임 생성자라고 하며, 이는 C++11부터 사용 가능하다.

그 이전에는 생성자 내에서 다른 생성자를 부를 수 없다.


2. 대입


3. 필드 초기화 (C++11부터 가능)



이중 초기화 리스트와 대입은 차이가 있는데, 

초기화 리스트는 마치 일반 변수에서 선언과 동시에 값을 입력하는 것과 같고

대입은 선언 후에 대입하는 것이다.


따라서 reference나 const는 선언과 동시에 값이 있어야 하기에 초기화 리스트로만 초기화 할 수 있다.

그리고 int, double 등이 아니라 class에 대입을 썼다면, 선언 시 디폴트 생성자가 불리고 그만큼 오버헤드가 존재한다.


따라서 대입 보다는 초기화 리스트를 사용하는 것이 권장된다.



* 초기화하지 않은 경우


초기화하지 않은 멤버 변수의 경우,

클래스라면 디폴트 생성자가 불린다.

Primitive type이라면 (int 등) 초기화되지 않는다.

Reference라면 컴파일 오류가 발생한다.



이 경우 x, y는 임의의 값이 나오게 된다.


참고: https://stackoverflow.com/questions/3127454/how-do-c-class-members-get-initialized-if-i-dont-do-it-explicitly

1. 생성자가 protected/private인 경우


생성자가 protected/private인 경우 일반적으로는 개체를 생성할 수가 없다.



이런 경우는 별도의 public static 함수를 제공해서 개체를 리턴시키는 경우이다.



참고링크: https://isocpp.org/wiki/faq/ctors#named-ctor-idiom



2. 소멸자가 protected/private인 경우


소멸자가 protected/private인 경우엔 stack에 해당 클래스의 변수를 생성할 수 없다.

이는 컴파일 시 stack 변수가 있는 bracket이 끝나면 소멸자를 호출해야 하는데, 소멸자가 protected/private 이어서 호출할 수 없기 때문이다.



이 경우는 해당 변수를 heap에 생성하면 된다. Heap 변수는 bracket이 끝나도 소멸자가 불리지 않기 때문이다.


다만 이 경우도 delete를 통해 heap 변수를 제거하려 하면 에러가 나기 때문에, 별도의 자기 자신을 파괴하는 destroy 함수를 제공해야 한다.




3. Protected 생성자/소멸자인 경우


Protected 생성자/소멸자인 경우에는 stack에 해당 클래스의 개체를 생성할 수는 없지만, 상속한 경우에는 가능하다.




혼자 프로그래밍을 하는 경우는 한 파일 내에서 선언과 구현을 해버리는 경우가 많으나, 


일반적인 경우는 선언부와 구현부를 나눠서 각각 헤더 파일과 소스 파일에 작성하게 된다.


이는 라이브러리 형태로 배포하게 될 경우 소스는 배포하지 않아 구현을 숨겼더라도 헤더파일만 보고 해당 코드의 기능을 쓸 수 있기 위함이기도 하고, 소스를 배포하더라도 가독성을 위해서 필요한 것이다.


이때 주의해야 할 내용을 정리해 본다.



1. 인라인, 템플릿 등 컴파일 시간에 어셈블리 코드가 치환되는 경우, 헤더 파일에 구현과 선언이 같이 있어야 한다.


아래 구현을 보자.

<헤더파일>




<소스파일>



이 헤더파일을 include하고 Stack 클래스를 가져다 쓰면 잘 컴파일 될 것 같지만 에러가 발생한다.

(만일 이 코드가 template이 아니었다면 정상 동작한다)


이는 가져다 쓰는 쪽을 컴파일 할 때 컴파일러가 헤더 파일을 보고 template을 치환해야 하는데, 구현부의 소스파일은 이미 컴파일 된 상태이기 때문에 컴파일러가 치환할 수 없기 때문이다.


따라서 이 코드가 잘 동작하려면 소스파일 내용을 헤더파일 내부로 옮겨야 한다.


template의 경우 선언/구현 둘 다 키워드가 필요한데,

inline의 경우 inline 키워드는 선언부, 구현부 한 군데에만 있거나 두 군데에 있어도, 만일 둘 다 inline이 없어도 선언과 구현이 모두 헤더파일에 있으면 암시적으로 inline이 되는 것 같다. (링크)



2. 디폴트 파라미터를 사용하는 경우 선언부에만 적어주어야 한다.


디폴트 파라미터를 선언부와 구현부 모두에 쓰면 컴파일 에러가 발생한다.


디폴트 파라미터를 선언부에는 쓰지 않고 구현부에만 쓰는 경우,

컴파일러가 함수 호출시 선언부를 보고 함수 호출이 올바른지 체크하게 되는데, 

선언부에는 디폴트 파라미터를 쓰지 않았으므로 호출에 대해 인자가 모자라다는 에러를 내게 된다.

즉, 디폴트 파라미터를 쓸 수 없게 된다.




3. 정적 멤버 변수와 정적 멤버 함수


정적 멤버 변수의 경우 외부 선언은 구현 파일에 작성한다.


정적 멤버 함수의 경우 선언부에만 static 키워드를 넣고 구현부에는 넣지 않는다.

구현부에 static 키워드를 넣으면 C의 static 키워드가 된다.



4. 상수 멤버 함수


상수 멤버 함수의 const 키워드는 선언부와 구현부 모두 넣는다.


5. 가상 함수


가상 함수를 나타내는 virtual은 선언부에만 적는다. (구현부에는 X)

+ Recent posts