👩🏻‍💻기초지식/C++

[C++] 상속의 문제점 : 오브젝트 슬라이싱 (Object Slicing)

공대 컴린이 2023. 10. 13. 21:36
728x90

정의

오브젝트 슬라이싱은 Class 상속과정에서 일어날 수 있는 문제로, 파생 클래스 객체가 기본 클래스 객체에 할당되면, 파생 클래스 객체의 추가적인 특성이 잘려나간 채 기본 클래스 객체로 생성되는 것을 말한다.

 

쉽게말해, 자식 객체에서 부모 객체로 copy 될 때 자식 객체의 정보가 잘려나가는 것이다.

 

주로 부모 클래스를 call by value로 받는 경우, 오브젝트 슬라이싱이 발생한다.

 

오브젝트 슬라이싱 발생 상황

#include <iostream>
using namespace std;

class Base {
protected:
	int i;

public:
	Base(int a) { i = a; }
	virtual void display()
	{
		cout << "Base Class Object's i: " << i << endl;
	}
};

class Derived : public Base {
private:
	int j;

public:
	Derived(int a, int b)
		: Base(a)
	{
		j = b;
	}
	virtual void display()
	{
		cout << "Derived Class Object's i : " << i << ", j : " << j << endl;
	}
};

// Global method, Base class
// object is passed by value
void somefunc(Base obj)
{
	obj.display();
}

int main()
{
	Base b(10);
	Derived d(20, 30);
	somefunc(b);

	// Object Slicing, d의 j 멤버가 슬라이싱 된다!
	somefunc(d);
	return 0;
}

 

> 출력 결과

Base Class Object's i: 10
Base Class Object's i: 20

 

기본 클래스인 Base 클래스를 상속받은 Derived 클래스가 존재할 때, Derived 객체를 (Base obj) 매개변수로 값에 의한 전달을 수행하면 Derived 클래스의 j 멤버 변수가 슬라이싱 되고, i 만 남게 된다.

 

Base의 display 가상함수를 Derived 함수에서 오버라이드하여 재정의했지만, Base obj의 매개변수로 전달되어 Base의 display 함수가 호출된다.

그리고 이때 복사생성자가 호출되며 Derived 객체에 초기화한 i = 20이라는 값이 copy 되어 i 값은 20으로 출력되는 것이다.

 

좀 더 자세히 보면, Base obj라는 매개변수로 전달되어 copy가 일어날 때, i 값은 일반 타입 데이터이기 때문에 복사가 일어나고, Derived 객체의 가상함수테이블은 포인터이기 때문에 복사되지 않는다. 따라서 Base obj는 Derived 객체의 가상함수 테이블을 가리키지 못하고 Base 객체의 가상함수 테이블을 가리켜 Base::display 함수가 호출되는 것이다.

 

이러한 문제는 somfunc의 함수에 Base obj 매개변수를 value 값으로 전달받았기 때문이다.

 

따라서, Base obj 매개변수를 포인터나 참조로 전달받으면 오브젝트 슬라이싱 문제가 발생하지 않는다.

 

포인터나 참조를 사용한 오브젝트 슬라이싱 해결

// 참조로 매개변수 전달받기
void somefunc(Base& obj)
{
	obj.display();
}

// 포인터로 받는 경우도 동일하다
void somefunc(Base* obj)
{
	obj->display();
}

 

> 출력 결과

Base Class Object's i: 10
Derived Class Object's i : 20, j : 30

 

복사생성자가 호출되어 copy가 일어나는 somefunc 함수를 참조나 포인터로 전달받으면 오브젝트 슬라이싱 문제가 발생하지 않는다.

 

오브젝트 슬라이싱을 방지하기 위한 방법

처음 프로그램을 설계할 때, 오브젝트 슬라이싱을 방지하기 위한 방법으로, 부모클래스의 복사생성자와 복사대입생성자의 생성을 막는 방법이 있다.

오브젝트 슬라이싱이 복사(copy)로 발생하는 것이기 때문에 원인을 제거하는 것이다.

 

#include <iostream>
using namespace std;

class Base {
protected:
	int i;

public:
	////// 복사생성자, 복사대입생성자 막기 ///////
	Base(const Base &other) = delete;
	Base operator =(const Base &other) = delete;
	////////////////////////////////////////////
	
	Base(int a) { i = a; }
	virtual void display()
	{
		cout << "Base Class Object's i: " << i << endl;
	}
};

class Derived : public Base {
private:
	int j;

public:
	Derived(int a, int b)
		: Base(a)
	{
		j = b;
	}
	virtual void display()
	{
		cout << "Derived Class Object's i : " << i << ", j : " << j << endl;
	}
};

int main()
{
	Derived d(20, 30);
	
	Base b = d;
	b.display();
	
	return 0;
}

 

위처럼, Base 클래스의 복사생성자와 복사대입생성자를 막아놓으면 Base b = d; 와 같이 오브젝트 슬라이싱이 일어나는것을 방지할 수 있다.

 

> function = delete;

더보기
직접 삭제 함수(deleted function)
위와 같은 = delete; 명령어는 일반 함수를 직접 삭제 함수로 만들어준다.
이는, 컴파일러에게 일부러 에러를 발생시키도록 하기위해 종종 사용된다.
보통, 암시적 변환에 의해 의도치 않은 함수 호출을 방지할때 사용한다.

뒤에서 등장할 =default 명령어는 특정 멤버 함수를 기본 구현으로 사용하겠다는 의미를 갖는다.
특히, 복사 생성자를 =default로 정의하면 컴파일러는 자동으로 기본 복사 생성자를 생성하도록 지시된다.

 

 

위처럼 삭제된 함수를 참조하려고 한다는 아주 친절한 에러문구와 함께 컴파일 오류를 발생시켜준다.

 

그러나, 이 방법은 파생클래스 간의 복사 또한 막아주기 때문에 원활한 프로그램을 설계하기 힘들다.

 

 

위와 같은 파생 객체 간 복사는 가능하도록 프로그램을 수정해야 하는데, 이는 Base의 복사생성자를 protected로 변경하기만 하면 된다.

 

 

protected로 설정하고 함수 설정을 delete가 아닌 default로 바꿔주었다.


참조

https://www.geeksforgeeks.org/object-slicing-in-c/

 

Object Slicing in C++ - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

https://velog.io/@kwt0124/%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1

 

오브젝트 슬라이싱 - 객체 slice

오브젝트 슬라이싱 예시를 위해서 추상클래스가 아닌 그냥 부모 클래스로 만듬(추상클래스는 부모 클래스로 객체 생성이 불가능하므로)\-> animal을 객체로 선언하면 딱 Animal 멤버 크기만큼의 데

velog.io

https://pretending.tistory.com/entry/C-Object-Slicing-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1

 

[C++] Object Slicing (오브젝트 슬라이싱)

Class의 상속과정에서 일어날 수 있는 문제 중의 하나인 Object Slicing에 대해서 알아본다. 그와 더불어 Operator Overloading에서 일어날 수 있는 문제에 대해서도 배워보자. 상속으로 인한 Onject Slicing (오

pretending.tistory.com

 

728x90