본문 바로가기
C++/Effective C++

Effective C++ (6) ~ (10)

by 계발자 망고 2021. 8. 9.

2. 생성자, 소멸자 및 대입 연산자

6) #컴파일러가 만들어낸 함수가 필요 없다면 확실히 금지시키자

복사 생성자와 복사 대입 연산자는 사용자가 선언하지 않을 때에도 컴파일러가 자동으로 만들어내는 대표적인 두 함수이다.

이 둘의 사용을 금지하기 위해 반드시 private으로 선언하자.

이 때 매개변수 이름은 생략해도 되고, 이렇게 선언된 기본 클래스를 만들어 상속하도록 활용할 수도 있다.

7) 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자

C++ 의 규정에 따르면, 기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때, 소멸자가 비 가상으로 선언되어 있다면 미정의 동작이 발생한다.

이를 막기 위해 언제나 기본 클래스에는 소멸자를 가상으로 선언한다면, 어떤 포인터로 객체를 삭제하더라도 메모리 누수없이 객체가 완벽하게 소멸될 것이다.

그러나, 파생될 가능성이 없는 즉 기본 클래스로 사용하지 않을 클래스의 소멸자를 가상으로 선언하는 것은 좋지 않다.

왜냐하면 클래스에 가상 함수가 포함되는 순간 객체에 가상 함수 테이블(vptr)을 포함하게 되어 크기가 커지기 때문이다.

이는 호환성과 이식성을 깨트리게 되어 중대한 문제를 발생시킬 수도 있다.

또 주의할 점은, std::string은 비가상 소멸자를 가지고 있으므로 이것의 파생 클래스를 만드는 일은 없어야 한다.

std 컨테이너 타입 (vector list set 등) 모두가 비가상 소멸자를 가지고 있으므로 주의하자.

C#에는 sealed, java에는 final 이라는 파생을 금지시키는 키워드가 있지만 C++에는 존재하지 않는다.

8) 예외가 소멸자를 떠나지 못하도록 붙들어 매자

예외를 내보내는 소멸자는 밖에서 좋지않은 결과, 미정의 동작을 발생시킬 확률이 있다.

만약, 데이터베이스 연결과 같은 자원 객체의 소멸자에서 예외가 발생할 가능성이 있다면, 자원 관리 클래스를 사용할 수 있다.

이 자원 관리 클래스는 3장에서 자세하게 다뤄지며, 자원 관리 클래스의 소멸자에서 자원의 소멸을 담당하도록 만드는 것이다.

그러나 이 자원 관리 클래스의 소멸자에서 예외가 발생할 수 있다.

이 때, 두가지 선택지가 있다.

  • 예외 발생시 std::abort 호출로 프로그램을 바로 끝내버린다.
  • 예외를 삼켜 로그를 출력한다. : 왜 예외가 발생했는지 정보가 사라져버린다.

둘 다 문제점이 있으므로, 사용자에게 Close 라는, 소멸자가 아닌 자원 소멸 함수를 제공함으로써 안전장치를 추가할 수 있다.

사용자는 이 Close 함수를 호출하여 직접 예외를 처리할 수 있는 기회를 갖게 된다.

중요한 것은, 소멸자에서 책임감없이 예외를 내뿜어서는 안된다는 점이다.

9) 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

파생 클래스의 생성 혹은 소멸의 과정을 떠올려보자.

생성은 기본 클래스에서 파생 클래스의 순서로, 소멸은 파생 클래스에서 기본 클래스의 순서로 진행된다.

기본 클래스의 생성자 마지막에 선언된 가상 함수 호출은 절대 파생 클래스 쪽으로 전파되어 호출되지 않는다.

기본 클래스의 생성자 호출 중에는 파생 클래스의 데이터 멤버가 초기화된 상태가 아니기 때문이다.

혹, 파생 클래스의 함수가 호출된다 하더라도 커다란 문제가 생길 가능성이 다분하므로, 그냥 C++은 애초에 이런식으로 사용하지 않도록 설계되었다.

만약 기본 생성자에서 파생 클래스의 정보가 반드시 필요한 경우엔 다음과 같이 파생 클래스의 생성자가 기본 클래스의 생성자로 정보를 올려보내 주도록 한다.

class Transaction(
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const; // 비가상 함수임에 주목
};

Transaction::Transaction(const std::string& logInfo)
{
logTransactio(logInfo);
}

class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters ) :
Transaction(createLogString(parameters))
{
...
}
private:
static std::string createLogString( parameters );
};

10) 대입 연산자는 *this의 참조자를 반환하게 하자

대입 연산자가 좌변 인자에 대한 참조자를 반환하도록 하는 것이 관례이므로, 사용자 정의 클래스 또한 이러한 규칙을 지킬 수 있도록 구현하자.

이는 단순 대입이 아닌 += 과 같은 형태의 대입 연산자에서도 마찬가지이다.

Widget& oprater=(const Widget& rhs)
{
return *this;
}

표준 라이브러리들 또한 지키고 있는 관례이므로 반드시 지킬 수 있도록 하자!

반응형

'C++ > Effective C++' 카테고리의 다른 글

Effective C++ (26) ~ (30)  (0) 2021.08.22
Effective C++ (21) ~ (25)  (0) 2021.08.16
Effective C++ (16) ~ (20)  (0) 2021.08.14
Effective C++ (11) ~ (15)  (0) 2021.08.12
Effective C++ (1) ~ (5)  (0) 2021.07.31

댓글