728x90

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.

https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg

+ Recent posts