항목 7: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자
기본 클래스에 virtual 소멸자가 없다면, 파생 클래스의 소멸자 실행이 건너뛰어져 발생하는 메모리릭을 방지하도록 해야함
- 가상 함수를 하나라도 가진 클래스 (=기본 클래스로 사용하겠다) --> 클래스의 소멸자도 가상 소멸자여야 함
- 자연스럽게 소멸자가 가상 소멸자로 되어 있으면 기본 클래스구나 하고 인식할 수 있음
- 추상 클래스(=기본 클래스로 쓰일 목적)로 만들고 싶은 클래스 --> 순수 가상 소멸자를 선언하자(=순수 가상함수 있으면 추상 클래스)
class Base
{
public:
Base();
virtual ~Base();
}
clase Object : public Base
{
public:
Object();
~Object();
}
int main()
{
Base *pBase = new Object;
delete pBase;
}
기본 클래스에서 소멸자를 가상 소멸자로 선언하지 않을 경우?
delete pBase를 할 경우 Base의 소멸자만 호출함 --> Object의 소멸자가 호출되어야함.
==> virtual ~Base(); 이를 막기 위해서 Base 클래스에는 virtual 소멸자를 사용하자 (파생클래스 소멸자는 생략 가능)
- 파생클래스 소멸자 --> 기본 클래스 소멸자 순으로 작동함
- 부모 클래스 변수에 자식 클래스를 동적할당 해야한다면 virtual을 붙여서 실제 인스턴스된 객체의 함수가 불릴 수 있도록 하자
- virtual을 붙이면 virtual table(가상 함수를 가리키는 function pointer table)을 갖게 되면서 class 사이즈가 커지게 되므로 --> 기본클래스/다형성을 갖도록 설계된 클래스에만 가상 소멸자를 선언하자)
- virtual 키워드를 쓰는 순간 32bit 체제에서는 32bit / 64bit 에서는 64bit 만큼 객체의 크기가 커지게 됨
(가상함수테이블(어떤 가상 함수를 호출해야 하는지 결정하는 정보)을 가리키는 포인터의 크기) - 생각없는 가상 소멸자 선언은 객체의 크기만 키움
- virtual 키워드를 쓰는 순간 32bit 체제에서는 32bit / 64bit 에서는 64bit 만큼 객체의 크기가 커지게 됨
#include <iostream>
using namespace std;
class A
{
public:
~A() {}
};
class B : public A
{
public:
~B() {}
};
int main()
{
//문제가 되는 경우
A* a = new b;
delete a;
//문제가 되지 않는 경우
B* b = new B;
delete b;
//std::string / STL 컨테이너 같은 타입 클래스들은 가상 소멸자가 없어 기본 클래스로 두게 되면 문제가 생김
//B를 생성하고 B를 지우기 때문에 문제가 되지 않음.
//but B를 만들고 A(ex) standard vector)를 지우면 메모리 누수가 될 수 있음
return 0;
}
항목 8: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자
소멸자가 호출되는 경우
- 정상적으로 객체가 종료되었을 때
- 예외처리 매커니즘에 의해 객체가 소멸될 때
어떤 동작이 예외를 일으키며 fail할 가능성이 있고 그 예외를 처리해야할 필요가 있다면
--> 소멸자가 아닌 다른 함수에서 먼저 처리하도록 하자
--> 일반 함수에서 예외 발생 시 소멸자에서 예외를 삼키던지/프로그램을 끝내던지 해야함
public:
~Object()
{
base.sth();
}
private:
Base base;
- sth()에서 예외가 발생하게 되면, 잘 될 수도 있지만 정의되지 않은 행동을 할 수도 있다.
- ==> try-catch 사용하여 예외가 다른 곳으로 전파되는 것을 막자
- 프로그램을 바로 끝내거나(abort()) 실패한 로그를 출력하거나 assert 걸거나
- ==> 또는 예외 발생이 소멸자가 아닌 다른 함수에서 비롯되어야 한다.
- void sth()
{
base.sth();
check = true;
}
- void sth()
- 소멸자에서는 예외가 빠져나가면 안된다. 소멸자에서 끝내야 함
- ==> try-catch 사용하여 예외가 다른 곳으로 전파되는 것을 막자
class DBConnector
{
public:
void close()
{
//일반 함수에서 실패 가능성 있는 동작을 실행
//정상 동작 시 true를 반환하는 것으로 가정
closed = db.close();
}
~DBConnector()
{
//소멸자에서 한 번 더 동작
if(!closed)
try
{
db.close();
}
catch()
{
//그래도 실패하면 로그 등 예외 처리
LOG("Failed");
}
}
private:
DBConnection db;
bool closed; //예외 발생 여부 먼저 체크
};
사용자 정의가 아닌 소멸자에서만 close()를 실행하게 되면 예외가 소멸자를 떠나게 됨.
PC버전은 assert를 걸고 임베디드는 Log를 남기자
try-catch?
==> cpp에선 사용하지 않음
refer: https://google.github.io/styleguide/cppguide.html#Exceptions
(c와 cpp를 같이 사용하는 케이스가 많은데 c에선 exception을 제공하지 않음)
(예외 처리를 위한 점프 코드를 넣어야 할 수도 있고)
항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자
초기화되지 않은 데이터 멤버는 정의되지 않은 상태에 있다!
생성자/소멸자 안에서 가상 함수를 호출하면 안됨
--> 가상함수여도 생성 중이거나 소멸 중일 떄는 파생 클래스와 관련이 없게 됨
Base 클래스를 상속받는 Derived 클래스 객체가 생성될 때, Base 생성자 내부 에서는 객체 타입이 Base임
--> 이때 가상 함수 호출 시 Base 함수가 호출됨
--> virtual table이나 Derived 클래스가 아직 생성/초기화 되지 않은 단계에서 함수를 잘못 부르면 어떤 동작을 할 지 알 수 없음
==> 객체 생성/소멸 시점에서 파생 클래스의 가상함수, 멤버는 초기화 되지 못했기 때문에 접근하지 못하게 막음 (C++)
==> =파생 클래스 생성/소멸을 위해 기본 클래스 생성/소멸자가 호출되는 시점에는 객체의 타입 = 기본 클래스 (파생 클래스의 미초기화 멤버에 접근을 막기 위해 내려가지 않는다)
🔹 해결책: 기본 클래스의 비가상 멤버 함수로 두고 파라미터로 필요한 정보를 기본 클래스에 넘기는 규칙을 두자
==> 파생 클래스에서 기본 클래스에 넘겨줄 파라미터를 return static 변수
static std::string createLogString( parameters );
class Transaction
{
public:
Transaction()
{
cout << "부모 생성자" << endl;
logTransaction();
}
//기본 클래스에서 가상 함수 호출 시 정의되어 있지 않으면 링크 에러남. (정의되어 있으면 더 골치 아픔, 기본 클래스에 정의된 가상 함수가 동작함)
//virtual void logTransaction() const; //LINK ERROR
virtual void logTransaction() { cout << "부모 logTransaction" << endl; };
};
class BuyTransaction : public Transaction
{
public:
BuyTransaction()
{
cout << "자식 생성자" << endl;
}
virtual void logTransaction() { cout << "자식 logTransaction" << endl; };
};
int main()
{
BuyTransaction buyTransaction;
return 0;
}
- 파생클래스 객체 생성 호출 순서
- 기본 클래스 생성자 호출 --> 기본 멤버 초기화 --> 파생 클래스 생성자 호출 --> 파생 멤버 초기화
- 기본 클래스 생성자가 돌아가는 시점에서 파생 클래스 데이터 멤버는 아직 초기화 되기 전이라 접근 불가
- 파생클래스 객체 소멸 호출 순서
- 파생 클래스 소멸 --> 파생 멤버 비정의(소멸) --> 기본 클래스 소멸 --> 기본 멤버 비정의(소멸)
'Programming > C++' 카테고리의 다른 글
작성중) 자료형과 타입 캐스팅 (0) | 2022.06.23 |
---|---|
[Effective C++] Chapter 2. 생성자, 소멸자 및 대입 연산자(1) (0) | 2022.06.12 |
[Visual Studio 2017] 단축키 모음 (0) | 2022.02.27 |
.a와 .so 라이브러리 (Dependency) (0) | 2022.02.26 |
[Effective C++] Chapter 1. C++에 왔으면 C++의 법을 따릅시다. (2) | 2022.02.15 |