[C++] Object Slicing (오브젝트 슬라이싱)
Class의 상속과정에서 일어날 수 있는 문제 중의 하나인 Object Slicing에 대해서 알아본다. 그와 더불어 Operator Overloading에서 일어날 수 있는 문제에 대해서도 배워보자.
상속으로 인한 Onject Slicing (오브젝트 슬라이싱) 문제점
Object Slicing이란 말 그래도 오브젝트 즉 객체가 Slicing 되는 것이다. 어떻게 해야지 Slicing이 되는지 Slicing이 되면 무슨 문제가 생기는지 아래 코드를 통해서 설명한다.
#include <iostream>
class Animal
{
public:
Animal() = default;
virtual void speak()
{
std::cout << "Animal" << std::endl;
}
virtual ~Animal() = default;
private:
double animalData = 0.0f;
};
class Dog : public Animal
{
public:
Dog(double data) : dogData{ data } {};
void speak() override
{
std::cout << "woof~" << std::endl;
}
private:
double dogData;
};
int main()
{
Dog choco{ 9.0 };
Animal cloneChoco = choco;
return 0;
}
Animal 클래스에는 멤버 변수 animalData, Dog 클래스에는 멤버 변수 dogData가 존재하고 main함수에서 Dog 객체인 choco를 선언하고 dogData를 9.0으로 초기화해주었다. 그다음 Animal 객체 cloneChoco를 Copy Constructor를 이용하여 Choco를 복사하여 객체를 생성하였다. 이 과정에서 생기는 문제가 바로 Object Slicing이다.
아래 그림은 Object Slicing이 일어나는 이유에 대해서 그린 그림이다.
Animal 객체인 cloneChoco에 choco오브젝트를 Copy Constructor로 복사할 경우 내부 Memory Layout에서 각 변수들이 어떻게 복사되는지 나타낸 그림이다. 위 그림에서 보면 Animal객체인 cloneChoco는 dogData를 가지고 있지 않기 때문에 dogData는 잘리게 된다. 또한 Vitual Table을 가리키는 특수한 변수 *vT는 복사가 이루어지지 않는다. 특히 아래 같이 함수를 정의한 경우 Object Slicing이 많이 일어난다.
void print_object(Animal other) const
{
std::cout << other.print() << std::endl;
}
위와 같이 함수 파라미터를 Animal형으로 정의를 할 경우 결국엔 Copy Assignment가 일어나기 때문에 Animal객체에 Dog객체를 Copy 한 것과 동일하게 Object Slicing을 초래하게 된다.
위 두 경우의 문제를 해결하는 방법은 포인터 혹은 레퍼런스를 이용하여 해당 객체를 가리키는 방법이다.
더 나아가서 Copy Constructor와 Copy Assignment를 delete를 통하여 아예 방지하는 방법도 있다. 아래 코드는 delete를 통한 Conpy Constructor와 Assignment를 사용 못하게 하는 방법이다.
#include <iostream>
class Animal
{
public:
Animal() = default;
Animal& operator=(const Animal& ohter) = delete;
Animal(const Animal& other) = delete;
virtual void speak()
{
std::cout << "Animal" << std::endl;
}
virtual ~Animal() = default;
private:
double animalData = 0.0f;
};
class Dog : public Animal
{
public:
Dog(double data) : dogData{ data } {};
void speak() override
{
std::cout << "woof~" << std::endl;
}
private:
double dogData;
};
int main()
{
Dog choco{ 9.0 };
Animal cloneChoco = choco; //ERROR
Dog cream{ choco }; //ERROR
return 0;
}
위와 같은 해결방법은 Base 클래스에서 직접적으로 Copy Constructor와 Copy Assignment를 delete 해줌으로 Animal 객체를 Dog객체로 생성하는 것을 방지해줬다. 하지만 또 다른 에러가 발생을 하게 되는데 그 에러는 바로 Dog객체 간의 Copy Constructor가 호출이 불가능 해진다는 점이다. 이유는 Base클래스에서 Copy Construcotr를 delete를 해주면 해당 속성이 Derived클래스에도 적용이 되기 때문이다. 그렇다면 다시 아래와 같이 수정할 수 있다.
#include <iostream>
class Animal
{
public:
Animal() = default;
Animal& operator=(const Animal& ohter) = delete;
virtual void speak()
{
std::cout << "Animal" << std::endl;
}
virtual ~Animal() = default;
protected:
Animal(const Animal& other) = default;
private:
double animalData = 0.0f;
};
class Dog : public Animal
{
public:
Dog(double data) : dogData{ data } {};
void speak() override
{
std::cout << "woof~" << std::endl;
}
private:
double dogData;
};
int main()
{
Dog choco{ 9.0 };
Animal cloneChoco = choco; //ERROR
Dog cream{ choco }; //OK
return 0;
}
Copy Constructor를 Dog 클래스에서 접근이 가능하고 외부에서는 접근이 불가능하게 protected키워드를 이용하여 선언을 해줌으로 문제가 해결이 가능하다.
상속으로 인해 Operator Overloading (연산자 오버 로딩)에서 생길 수 있는 문제점
Animal 객체에 대해서 ==연산자를 오버 로딩을 하면 아래 함수와 같다.
bool operator==(const Animal& lhs, const Animal& rhs)
{
return lhs.animalData == rhs.animalData;
}
위와 같이 함수 오버 로딩을 하였을 경우 생기는 문제는 Animal레퍼런스로 Dog을 가리킬 수 있기 때문에 Dog객체 간 ==연산자를 사용할 경우 implicit 하게 위 함수를 호출하게 된다. Dog객체 간 ==연산자의 사용하는 이유의 핵심은 "dogData가 같나"를 비교하는 것이지만 본의 아니게 animalData가 같은지만 확인하는 결과가 나온다. 위를 방지하기 위해 Dog객체에 대한 ==연산자 오버 로딩을 해주어야 한다.
bool operator==(const Animal& lhs, const Animal& rhs)
{
return lhs.animalData == rhs.animalData;
}
bool operator==(const Dog& lhs, const Dog& rhs)
{
return lhs.animalData == rhs.animalData && lhs.dogData == rhs.dogData;
}
*위 함수 모두 편의상 animalData와 dogData를 public키워드에 선언을 했다고 가정하고 짠 함수이다.
이렇게 상속으로 인해서 발생할 수 있는 문제 두 가지를 다루어봤다.
Ref.
'Modern C++' 카테고리의 다른 글
[C++] Smart Pointer (스마트 포인터): Unique Pointer (유니크 포인터) (0) | 2021.08.23 |
---|---|
[C++] Smart Pointer Intro (스마트 포인터) (0) | 2021.08.22 |
[C++] Virtual Inheritance (가상 상속) : Diamond Problem (다중 상속 문제) 해결 (0) | 2021.08.21 |
[C++] Multiple Inheritance (다중 상속) (0) | 2021.08.21 |
[C++] Pure Virtual Function (순수 가상 함수), Abstract Class (추상 클래스) (0) | 2021.08.20 |