C++

C++ 가상 복사 생성자(Virtual Copy Constructor) 총정리

김구티2 2024. 4. 9. 19:18

1. 가상 복사 생성자의 개념

이전 가상 생성자 글에서 런타임까지 유형이 결정되지 않는 객체를 생성하는 방법을 살펴봤다. 객체가 정확한 클래스 유형인지 알지 못한 채 생성하는 것이 가능할까? 여기서 등장하는 것이 바로 가상 복사 생성자다. 이것이 이 문제를 해결한다.


간혹 우리는 기존의 다른 객체로부터 객체를 구성해야 할 필요가 있을 수 있다. 그리고 엄밀히 말하면, 복사 생성자가 같은 일을 한다. 새로운 객체의 초기 상태는 기존의 다른 객체 상태를 기반으로 한다. 컴파일러는 객체가 다른 객체로부터 인스턴스화될 때 복사 생성자를 호출한다. 그러나 컴파일러는 적절한 복사 생성자를 호출하기 위해 구체적인 유형 정보가 필요하다.

 

예시:

#include <iostream>
using namespace std;

class Base
{
public:
     //
};

class Derived : public Base
{
public:
     Derived()
     {
          cout << "Derived created" << endl;
     }

     Derived(const Derived &rhs)
     {
          cout << "Derived created by deep copy" << endl;
     }

     ~Derived()
     {
          cout << "Derived destroyed" << endl;
     }
};

int main()
{
     Derived s1;

     Derived s2 = s1; // Compiler invokes "copy constructor"
                                // Type of s1 and s2 are concrete to compiler

     // How can we create Derived1 or Derived2 object
     // from pointer (reference) to Base class pointing Derived object?

     return 0;
}

 

어떤 종류의 객체에서 복사 구성을 할지 결정할 수 없다면 어떻게 될까? 예를 들어, 가상 생성자는 어떤 입력을 기반으로 런타임에 클래스 계층 구조의 객체를 만든다. 가상 생성자가 만든 다른 객체에서 객체를 복사하려고 할 때, 일반적인 복사 생성자를 사용할 수 없다. 런타임에 객체를 복제할 수 있는 특별한 복제 기능이 필요하다.


예를 들어, 도면 응용 프로그램을 생각해 보자. 캔버스에 이미 그려진 객체를 선택하여 동일한 객체의 인스턴스를 하나 더 붙여넣을 수 있다. 그런데 어떤 객체를 복붙할지 결정할 수 없다. 우리는 여기서 도움을 줄 가상 복사 생성자가 필요한 것이다.

 

예시:

#include <iostream>
using namespace std;

//// LIBRARY START
class Base
{
public:
     Base() { }

     virtual // Ensures to invoke actual object destructor
          ~Base() { }

     virtual void ChangeAttributes() = 0;

     // The "Virtual Constructor"
     static Base *Create(int id);

     // The "Virtual Copy Constructor"
     virtual Base *Clone() = 0;
};

class Derived1 : public Base
{
public:
     Derived1()
     {
          cout << "Derived1 created" << endl;
     }

     Derived1(const Derived1& rhs)
     {
          cout << "Derived1 created by deep copy" << endl;
     }

     ~Derived1()
     {
          cout << "~Derived1 destroyed" << endl;
     }

     void ChangeAttributes()
     {
          cout << "Derived1 Attributes Changed" << endl;
     }

     Base *Clone()
     {
          return new Derived1(*this);
     }
};

class Derived2 : public Base
{
public:
     Derived2()
     {
          cout << "Derived2 created" << endl;
     }

     Derived2(const Derived2& rhs)
     {
          cout << "Derived2 created by deep copy" << endl;
     }

     ~Derived2()
     {
          cout << "~Derived2 destroyed" << endl;
     }

     void ChangeAttributes()
     {
          cout << "Derived2 Attributes Changed" << endl;
     }

     Base *Clone()
     {
          return new Derived2(*this);
     }
};

class Derived3 : public Base
{
public:
     Derived3()
     {
          cout << "Derived3 created" << endl;
     }

     Derived3(const Derived3& rhs)
     {
          cout << "Derived3 created by deep copy" << endl;
     }

     ~Derived3()
     {
          cout << "~Derived3 destroyed" << endl;
     }

     void ChangeAttributes()
     {
          cout << "Derived3 Attributes Changed" << endl;
     }

     Base *Clone()
     {
          return new Derived3(*this);
     }
};

// We can also declare "Create" outside Base.
// But is more relevant to limit it's scope to Base
Base *Base::Create(int id)
{
     // Just expand the if-else ladder, if new Derived class is created
     // User need not be recompiled to create newly added class objects

     if( id == 1 )
     {
          return new Derived1;
     }
     else if( id == 2 )
     {
          return new Derived2;
     }
     else
     {
          return new Derived3;
     }
}
//// LIBRARY END

//// UTILITY SRART
class User
{
public:
     User() : pBase(0)
     {
          // Creates any object of Base hierarchy at runtime

          int input;

          cout << "Enter ID (1, 2 or 3): ";
          cin >> input;

          while( (input != 1) && (input != 2) && (input != 3) )
          {
               cout << "Enter ID (1, 2 or 3 only): ";
               cin >> input;
          }

          // Create objects via the "Virtual Constructor"
          pBase = Base::Create(input);
     }

     ~User()
     {
          if( pBase )
          {
               delete pBase;
               pBase = 0;
          }
     }

     void Action()
     {
          // Duplicate current object
          Base *pNewBase = pBase->Clone();

          // Change its attributes
          pNewBase->ChangeAttributes();

          // Dispose the created object
          delete pNewBase;
     }

private:
     Base *pBase;
};

//// UTILITY END

//// Consumer of User (UTILITY) class
int main()
{
     User *user = new User();

     user->Action();

     delete user;
}

 

가상 생성자의 도움을 받아 객체를 생성하는 사용자 클래스를 볼 수 있다. 생성할 객체는 사용자 입력을 기반으로 한다. Action()은 생성 중인 객체를 복제하고, 그 객체의 속성을 수정하는 것이다. 복제된 객체는 가상 복사 생성자로도 간주되는 Clone() 가상 함수의 도움을 받아 생성된다. Clone() 메서드 뒤에 있는 개념은 프로토타입 패턴의 빌드 블록이다.

728x90