[C++/Syntax] 상속2

상속의 성질과 포인터

상속은 다양한 성질을 지니고 있다.
그 다양한 성질과 포인터가 만나게 되면 더욱 다양한 성질을 띄게 되는데,
이는 C++에서의 객체를 사용함에 있어서 다양한 방식을 제공하게 해준다.


객체 상속과 포인터

class Test {
private:
	int Testnum;
public:
	Test(int num) {
		this->Testnum = num;
	}
	void test() { std::cout << "test in Test" << std::endl;}
};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num)
		: Test(num)
	{
		this->Test2num = num;
	}
	void test() { std::cout << "test in Test2" << std::endl;}
};

int main(){
	Test *test = new Test2(10);
	test->test();
	return 0;
}

위 코드의 main 함수에 Test *test = new Test2(10);을 보자 분명히 Test 형 포인터 인데 Test2를 넣고 있다.
이말인 즉슨, 부모클래스형 포인터는 자식 클래스의 포인터를 담을 수 있다는 뜻이다.

근데 이게 동작이 굉장히 신기한데,  main의 test->test();함수를 실행 시키면, Test의 test()함수 결과인 “test in Test”가 출력된다.
즉 같은 함수가 있을 땐 자료형의 함수를 불러온다는 뜻이다.

참고로, 만약
int main(){
    Test2 *test2 = new Test2(10);
    Test *test = test2;
    test2->test();
    test->test();
    return 0;
}

해당 코드를 실행하면
“test in Test2”
“test in Test”가 출력된다. 즉, 포인터의 자료형에 따라 불러올 함수가 결정 된다는 거다.

하지만 이는 자식 클래스에서 오버라이딩한 이유가 없어지게 되며, 문제가 있다고 볼 수 있다.
이를 해결하기 위해 도입하는 개념이 바로 Virtual Function, 가상함수이다.

 

가상함수 Virtual Function

가상함수의 선언은 virtual키워드 선언을 통해서 이뤄진다.
이후 오버라이딩을 하게 되면 자료형의 관계없이 오버라이딩 된 함수가 사용된다.

class Test {
private:
	int Testnum;
public:
	Test(int num) {
		this->Testnum = num;
	}
	virtual void test() { std::cout << "test in Test" << std::endl; }

};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num)
		: Test(num)
	{
		this->Test2num = num;
	}
	void test() { std::cout << "test in Test2" << std::endl; }
};

int main(){
	Test2 *test2 = new Test2(10);
	Test *test = test2;
	test2->test();
	test->test();
	return 0;
}

Test 클래스에 test() 함수 앞에 virtual 선언이 되어 있다.
즉, 이후 상속 받은 Test2에서 해당 함수를 오버라이트할 경우 자료형의 관계없이 Test2의 test()를 불러오란 뜻이다.

 

가상 함수와 소멸자

class Test {
private:
	int Testnum;
public:
	Test(int num) {	this->Testnum = num; }
	virtual void test() { std::cout << "test in Test" << std::endl; }

	~Test() {
		std::cout << "delete Test" << std::endl;
	}
};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num) : Test(num){ this->Test2num = num; }
	virtual void test() { std::cout << "test in Test2" << std::endl; }

	~Test2(){
		std::cout << "delete Test2" << std::endl;
	}
};

int main(){
	Test *test = new Test2(10);
	delete test;
	return 0;
}

위 코드를 실행하면 “delete Test” 만 뜨게 되며 ~Test() 소멸자만 뜨게 된다.

즉, 클래스에서 소멸자의 호출 또한 “자료형”의 소멸자만이 호출된다.
이는 메모리 leak이 일어 날 수 있기에 이 또한 virtual 키워드를 통해 해결 할 수 있다.

virtual ~Test() { ... }와 같이 바꿔주면,
“delete Test2
delete Test” 라는 결과가 뜨며, ~Test2()와 ~Test()가 순서대로 소멸 하는 것을 볼 수 있다.

 

++ 순수 가상함수

클래스 중에서 객체 생성을 목적으로 정의되지 않는 클래스도 있다고한다.
이때 virtual void test() = 0;이렇게 정의하게 되면, 
Test test = new Test(1)와 같이 객체를 생성하려고 하면 컴파일 에러가 발생한다.

class Test {
private:
	int Testnum;
public:
	Test(int num) {
		this->Testnum = num;
	}
	virtual void test() = 0;
};
class Test2 : public Test {
private:
	int Test2num;
public:
	Test2(int num)
		: Test(num)
	{
		this->Test2num = num;
	}
	void test() { std::cout << "test in Test2" << std::endl; }
};

int main(){
	Test2 *test2 = new Test2(10);
	Test *test = test2;
	test2->test();
	test->test();
	return 0;
}

Test 클래스의 virtual void test() = 0;은 new Test(1)과 같은 Test 객체를 생성할시 컴파일 에러를 출력한다.

즉, Test 클래스는 상속을 위한 클래스이며,
객체로 생성되어 사용되어선 안되고,
test()함수는 상속 받은 클래스에서 오버라이트 해야 된다는 의미다.

 

+++ 참조(Reference)

포인터 뿐만이 아니라 참조자 또한 같은 동작을 한다.

int main(){
	Test2 test2(10);
	Test &test = test2;
	test.test();
	return 0;
}

단순히 “->”가 “.”으로 선언 방식에서 살짝의 차이를 제외하곤 포인터와 동일한 동작을 한다.

카테고리C++

글의 문제가 있다면 댓글을 달아 주세요.

This site uses Akismet to reduce spam. Learn how your comment data is processed.