C++/기타

Virtual 소멸자 이유

sseram 2022. 5. 16. 19:50
반응형

 

기본 코드

 

class Base {
public:
	Base() { std::cout << "Base()" << std::endl; }
	Base(int a){ std::cout << "Base(int)" << std::endl; }
	~Base()    { std::cout << "~Base()" << std::endl; }
};
class Derived : public Base {
public:						
	Derived()		{ std::cout << "Derived()" << std::endl; }
	Derived(int a)	{ std::cout << "Derived(int)" << std::endl; }
	~Derived()		{ std::cout << "~Derived()" << std::endl; }
};
int main()
{
	Derived d2(5);
}

 

출력은... 

 

Derived(int a) 생성자를 호출하기 전, 상속받은 Base의 기본 생성자를 먼저 호출한다.

Derived(int a) 라고 되어있지만, 컴파일러는 자연스래 Derived(int a) : Base() {}  로 변환하여 진행.

 

소멸될 때엔 derived 의 소멸자를 부르고, base의 소멸자를 부른다.

 

 

cpp를 사용하다보면 다형성을 사용하기 위해 자연스래 기반 클래스의 포인터로 변수를 정의해 두고,

거기에 상속받은 클래스를 대입하여 사용을 하곤 하는데

 

->>

class Base
{
public:
	Base() { std::cout << "Base()" << std::endl; }
	Base(int a) { std::cout << "Base(int)" << std::endl; }
	~Base() { std::cout << "~Base()" << std::endl; }

	virtual void do_something() = 0;
};
class Derived : public Base
{
public:
	Derived() { std::cout << "Derived()" << std::endl; }
	Derived(int a) { std::cout << "Derived(int)" << std::endl; }
	~Derived() { std::cout << "~Derived()" << std::endl; }

	void do_something() override { std::cout << "hihi!" << std::endl; };
};
int main()
{
	std::unique_ptr<Base> v;
	v = std::make_unique<Derived>(5);
	v->do_something();
}

 

 위와 같이.

 

 

실행을 시켜 보면, 원하는 결과인 "hihi" 는 잘 출력이 되지만, 어째서인지 소멸자가 제대로 불리지 않는다.

 

 

v는 Base  type이므로, 소멸자가 불릴 때 자연스럽게 Base 소멸자만 불린다.

이걸 해결하기 위해서 소멸자에게 virtual을 붙여주면

 

 

class Base
{
public:
	Base() { std::cout << "Base()" << std::endl; }
	Base(int a) { std::cout << "Base(int)" << std::endl; }
	virtual ~Base() { std::cout << "~Base()" << std::endl; }

	virtual void do_something() = 0;
};
class Derived : public Base
{
public:
	Derived() { std::cout << "Derived()" << std::endl; }
	Derived(int a) { std::cout << "Derived(int)" << std::endl; }
	~Derived() { std::cout << "~Derived()" << std::endl; }

	void do_something() override { std::cout << "hihi!" << std::endl; };
};
int main()
{
	std::unique_ptr<Base> v;
	v = std::make_unique<Derived>(5);
	v->do_something();
}

원하는 결과를 얻을 수 있다.

 

 

 

 

 

 

아니면. virtual을 붙지지 않고 기반 클래스의 소멸자를 protected로 숨겨야 한다.

 

이번 예제는 unique_ptr 사용하지 말고, 좀 직관적으로 보게 코드를 살짝 바꿔보면

 

class Base
{
protected:
	~Base() { std::cout << "~Base()" << std::endl; }
public:
	Base() { std::cout << "Base()" << std::endl; }
	Base(int a) { std::cout << "Base(int)" << std::endl; }
	

	virtual void do_something() = 0;
};
class Derived : public Base
{
public:
	Derived() { std::cout << "Derived()" << std::endl; }
	Derived(int a) { std::cout << "Derived(int)" << std::endl; }
	~Derived() { std::cout << "~Derived()" << std::endl; }

	void do_something() override { std::cout << "hihi!" << std::endl; };
};
int main()
{
	Base* v = new Derived(2);
	delete v;
}

 

이런 식으로.

이렇게 하면, protected로 밖에서 안 보이게 해뒀으니 delete v 부분에서 소멸자에 접근할 수 없다고 에러가 뜬다.

어떻게 해야 하나?

걍 delete v; 를 지워버리면 에러도 없고 마음도 편안하겠지만 소멸자는 절대 불리지 않을 것이다.

 

그럼 부르려면 어떻게 해야 하나..? 

 

좀만 더 생각해보면, v는 type만 Base*일 뿐, 실제론 Derived class인걸 확인 할 수 있다.

다음과 같이 변경하고

 

 

class Base
{
protected:
	~Base() { std::cout << "~Base()" << std::endl; }
public:
	Base() { std::cout << "Base()" << std::endl; }
	Base(int a) { std::cout << "Base(int)" << std::endl; }
	

	virtual void do_something() = 0;
};
class Derived : public Base
{
public:
	Derived() { std::cout << "Derived()" << std::endl; }
	Derived(int a) { std::cout << "Derived(int)" << std::endl; }
	~Derived() { std::cout << "~Derived()" << std::endl; }

	void do_something() override { std::cout << "hihi!" << std::endl; };
};
int main()
{
	Base* v = new Derived(2);
	delete static_cast<Derived*>(v);
}

 

실행하면, 원하는 결과가 뜬다.

 

기반 클래스에서 소멸자를 만들 땐, virtual로 만들거나 non-virtual protected로 만들어

 

1. 자연스래 상속받은 클래스에서 소멸자를 전부 부를 수 있게 하거나,

2. user에게 상속받은 클래스의  소멸자까지 부를 수 있도록 강제해주자.

 

 

 


+)

unique ptr일 시, 소멸자를 따로 지정해주자

class Base
{
protected:
	~Base() { std::cout << "~Base()" << std::endl; }
public:
	Base() { std::cout << "Base()" << std::endl; }
	Base(int a) { std::cout << "Base(int)" << std::endl; }
	

	virtual void do_something() = 0;
};
class Derived : public Base
{
public:
	Derived() { std::cout << "Derived()" << std::endl; }
	Derived(int a) { std::cout << "Derived(int)" << std::endl; }
	~Derived() { std::cout << "~Derived()" << std::endl; }

	void do_something() override { std::cout << "hihi!" << std::endl; };
};

void my_Deleter(Base *p) {
	delete static_cast<Derived*>(p);
}

int main()
{
	std::unique_ptr<Base, decltype(&my_Deleter)> v(new Derived(5), &my_Deleter);
	v->do_something();
}

 

 

 

참고 : CppCoreGuideline. C.35

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-dtor-virtual

반응형