본문 바로가기

C, C++/Effective C++

[Effective C++] 2장 C++의 규칙 따르자

https://product.kyobobook.co.kr/detail/S000001962302

 

Effective C++ | 스콧 마이어스 - 교보문고

Effective C++ | [Effective C++]은 C++ 프로그래밍과 설계 기술을 향상시켜 주는 55가지 명쾌한 테크닉을 모은 책이다.

product.kyobobook.co.kr


Item5: C++이 암묵적으로 호출하는 함수 생각하기

c++컴파일러는 선언하지 않는다면 복사 생성자, 복사 대입 연산자, 소멸자를 자동으로 생성합니다.

생성자가 아예 없다면 생성자도 만들어줍니다.

 

만약 클래스 멤버에 참조자가 있는 등 생성할 수 없다면 컴파일러가 오류를 뱉습니다.


Item6: 컴파일러가 만드는 함수가 필요없는 경우 delete하자.

함수가 자동적으로 만드는 함수의 경우 public이 됩니다.

만약 컴파일러가 만드는 함수가 필요없다면 이를 사용하지 않기 위해 1. private으로 만든다, 2. Uncopyable을 private으로 상속받는다. 3. =delete를 통해 생성을 막는다.


Item7: 다형성 클래스의 소멸자는 virtual로 만들자.

기본적으로 C++에서 다형성 함수는 virtual table을 통해 동작합니다.

객체의 주소 안의 첫 값은 다른 멤버들이 아닌 virtual table이 존재하는 주소를 가지게 됩니다.

x86의 경우 4바이트, x64의 경우 8바이트 값이겠죠. 

 

그곳을 타고 들어가면 해당 클래스가 가진 가상 함수들의 주소가 들어가있습니다.

이러한 가상함수 테이블의 세팅은 생성자에서 이루어지는데, 자세한 사항은 밑의 포스팅을 확인하시면 됩니다.

https://basaeng.tistory.com/41

 

가상 함수 virtual

개요c++에서 virtual은 상속 개념에서 사용된다. java에서는 상속 관계에서 모든 함수가 가상 함수로 사용되지만, c++에서는 virtual을 명시하는 것과 아닌 것에 큰 차이가 생기기 때문에 유의해서 사

basaeng.tistory.com

 

아무튼 다형성을 통해 만들어진 클래스는 상위 클래스의 소멸자가 virtual이어야 캐스팅 했을 때 정상적으로 소멸자를 찾아서 호출하기 때문에 virtual로 만드는 것이 필수적입니다.

 

물론 가상함수가 필요없는 경우에 가상함수를 사용한다면 virtual 함수 호출 오버헤드와 함수에 대한 의미 전달이 잘못될 수 있습니다.


Item8: 소멸자 안에서 예외가 발생하지 않도록 하자.

소멸자 안에서는 exception이 발생하지 않는 것이 좋습니다.

기본적으로 객체 안에 여러 멤버들이 있다면 모든 멤버들이 정상적으로 소멸되지 못한채 빠져나올 수 있으며, 

이로인해 메모리 누수 등이 발생할 수 있습니다.

 

기본적으로 현재 소멸자는 noexcept취급인데 noexcept인 경우 exception이 발생하는 순간 중대한 오류로 여겨 terminate되기 때문에 유의해야 합니다.


Item9: 생성자, 소멸자 호출 중 가상 함수 호출하지 말자.

가상 함수를 호출한다는 것은 해당 클래스가 다형성으로 이루어진 클래스라는 것을 의미합니다.

Item7에서 보았듯이 다형성 클래스는 virtual table이 별도로 존재하는데요, 생성자와 소멸자에서 이 가상함수 테이블을 갈아끼며 동작하기 때문에 의도한대로 함수가 호출되지 않습니다.

 

그리고 기본적으로 직관적이지도 않은 코드가 됩니다.


Item10: 대입 연산자는 *this의 참조자를 반환하자

this는 해당 객체의 주소이고 *this는 해당 객체의 값입니다. 결론적으로 *this의 참조자를 반환하자는 것은 대입 연산을 하는 해당 객체에 chain연산이 가능하도록 만드는 것과 같습니다.

 

만약에 참조자가 아닌 value를 반환한다면 기본적으로 복사도 일어나며, 아예 새로운 값 rvalue가 반환되기 때문에 대입이 불가하겠죠


Item11: operator= 사용 시 자기 자신에 대한 대입을 처리하자. 

operator=을 구현할 때 자기 자신에 대한 대입 처리를 잊는 경우가 있습니다.

기본적으로 로직에서 전체 경우에 대해 순회하는 등의 행위로 본인이 본인에게 대입을 시도하는 일은 생각보다 흔한 일입니다.

 

이 때 자신에 대한 처리를 고려하지 않는다면, 본인의 자원을 해제하고 새로운 자원을 대입했는데 그 새로운 자원이 나여서 이미 해제되어있는 벙찌는 상황이 발생할 수 있습니다.

 

그래서

1. 본인에 대한 조건문 처리

2. 넘어온 데이터 사본을 만들고 swap처리

3. 아예 인자를 reference가 아닌 value로 받고 처리

등이 있겠습니다.


 

Item12: 객체의 모든 부분을 남김없이 복사하자.

C++에서 잘 만들어진 객체지향 프로그램이라면 객체의 복사는 복사 생성자와 복사 대입 연산자를 통해서만 이루어질 것입니다.

 

이전에 언급했듯이 기본적으로 컴파일러가 만들어주는 복사 생성자, 복사 대입 연산자는 각각에 멤버에 대한 복사를 그대로 만들어줍니다.

따라서 만약 수정이 필요하다면 복사를 위한 구현을 직접 만들 것입니다.

 

만약 직접 복사 생성자, 복사 대입 연산자를 만들었다면 이후 부터는 컴파일러가 별도로 개입하지 않기 때문에, 클래스에 멤버가 추가 되거나, 삭제 되었을 때 이에 대한 반영이 즉각적으로 이루어져야 합니다.

 

추가적으로 복사 생성자와 복사 대입 연산자는 구현이 비슷해지는 경우가 많은데, 그렇다고 해서 한쪽에서 다른 하나를 호출해서는 안됩니다.

 

생성자에 안에서 대입 연산자를 호출한다면 완전히 생성되지도 않은 객체에 대입을 하는 것이기 때문에 말이 안되고, 

대입 연산자 안에서 생성자를 호출한다면, 이미 만들어진 객체 안에서 새로운 객체를 생성하는 논리가 어긋난 상황이 발생하게 됩니다.