상속 받은 클래스의 생성자 소멸자 호출 순서

class A
{
}

class B : public A
{
}

class C : public B
{
}

위와 같은 클래스들이 있을때

CChild cc 클래스를 생성하면 생성자 호출 순서는

A -> B -> C 

소멸자 호출 순서는

C -> B -> A 이다.

부모 클래스 소멸자에 virtual 사용해야 하는 이유

A* Parent = new C()

부모 클래스의 포인터로 자식 클래스를 호출할때
가상 함수로 정의되지 않은 자식 클래스의 함수를 호출하면 부모 클래스의 멤버 함수가 호출된다.
소멸자도 자식 클래스의 소멸자가아닌 부모클래스의 소멸자가 호출이된다.

가상 함수로 소멸자가 사용되었다면 자식 클래스에서 재정의 될 수 있음을 명시하기 때문에 자식 클래스의 소멸자부터 차례대로 부모 클래스의 소멸자가 호출된다.

디폴트 코드

#include <iostream>

class A
{
public:
	A()
	{
		std::cout << "A생성자" << std::endl;
	}
};

class B : public A
{
public:
	B()
	{
		std::cout << "B생성자" << std::endl;
	}
};

class C : public B
{
public:
	C()
	{
		std::cout << "C생성자" << std::endl;
	}
};

int main()
{
	A* a = static_cast<A*>(new C());
	std::cout << std::endl;
	delete a;
}

결과

생성자 호출 순서

소멸자 코드

#include <iostream>

class A
{
public:
	A()
	{
		std::cout << "A생성자" << std::endl;
	}
	~A()
	{
		std::cout << "A소멸자" << std::endl;
		std::cout << std::endl;
	}
};

class B : public A
{
public:
	B()
	{
		std::cout << "B생성자" << std::endl;
	}
	~B()
	{
		std::cout << "B소멸자" << std::endl;
		std::cout << std::endl;
	}
};

class C : public B
{
public:
	C()
	{
		std::cout << "C생성자" << std::endl;
	}
	~C()
	{
		std::cout << "C소멸자" << std::endl;
		std::cout << std::endl;
	}
};

int main()
{
	A* a = static_cast<A*>(new C());
	std::cout << std::endl;
	delete a;
}

결과

일반 소멸자

Virtual 소멸자 코드

#include <iostream>

class A
{
public:
	A()
	{
		std::cout << "A생성자" << std::endl;
	}
	virtual ~A()
	{
		std::cout << "A소멸자" << std::endl;
		std::cout << std::endl;
	}
};

class B : public A
{
public:
	B()
	{
		std::cout << "B생성자" << std::endl;
	}
	~B()
	{
		std::cout << "B소멸자" << std::endl;
		std::cout << std::endl;
	}
};

class C : public B
{
public:
	C()
	{
		std::cout << "C생성자" << std::endl;
	}
	~C()
	{
		std::cout << "C소멸자" << std::endl;
		std::cout << std::endl;
	}
};

int main()
{
	A* a = static_cast<A*>(new C());
	std::cout << std::endl;
	delete a;
}

결과

자식 부터 차례대로 소멸자 호출.

만약 virtual 키워드가 B클래스에 달린다면?

int main()
{
	A* a = static_cast<A*>(new C());
	B* b = (B*)a;
	C* c = (C*)a;
	std::cout << std::endl;
	delete a;
}

B*, C*를 추가해 a의 주소값을 가져왔다.

위 사진과 같이 a,b,c의 주소값이 동일한걸 확인 할 수 있다.

class A
{
public:
	A()
	{
		std::cout << "A생성자" << std::endl;
	}
	~A()
	{
		std::cout << "A소멸자" << std::endl;
		std::cout << std::endl;
	}
};

class B : public A
{
public:
	B()
	{
		std::cout << "B생성자" << std::endl;
	}
	virtual ~B()
	{
		std::cout << "B소멸자" << std::endl;
		std::cout << std::endl;
	}
};

A클래스 소멸자에 있던 virtual 키워드를 B클래스 소멸자로 옮겨보았다.

b와c의 주소 값이 a와 달라진걸 확인 할 수 있다.

이 상태에서 delete a를하게 되면 에러가 발생한다.

int main()
{
	//A* a = static_cast<A*>(new C());
	A* a = reinterpret_cast<A*>(new C());
	B* b = (B*)a;
	C* c = (C*)a;
	std::cout << std::endl;
	delete a;
}

cast방식을 reinterpret로 cast를 진행하면 에러는 발생하지 않지만
소멸자는 A클래스의 소멸자가 호출이된다.

new C를 하는순간 가상함수 테이블이 생성되는데
A에는 가상함수가 없어서 가상함수 테이블이 생성되지 않고 가상함수 포인터 (vfptr을 가지고 있지 않는다)
또한 MSDN을 살펴보면 static_cast 를 하는순간 포인터의 주소가 변경될 수 있어서 위험하다고 명시하고있다!
그래서 a의 소멸자가 호출 되는 순간 펑!

+ Recent posts