C, C++

[C++] More Effective C++ Item 25 : 생성자 함수와 비 멤버 함수를 가상 함수처럼 만드는 방법

Binceline 2014. 3. 16. 12:45

More Effective C++ Item 25


생성자의 호출은 만들고자 하는 객체의 타입을 정확히 아는 상태에서만 가능하다.

가상 생성자라는 것은 사실 뭔가 이상하다..

생성자라는 것은 객체가 생성될 때 호출되야 하는 것인데 이걸 가상으로 한다는 건.. ㅎㅎ

여기서 말하고 싶은 가상 생성자는 그런 일반의 생성자와는 거리가 좀 있다. 

여기서 말하는 가상 생성자는 사실 '가상의 타입을 토대로 객체를 생성하는 함수'를 그렇게 부른 것이다.

물론 객체를 생성하는 것이기 때문에 생성자처럼 동작해야 하는 것은 맞다. 그렇기 때문에 가상 생성자라고 부르는 것이다.


이 가상 생성자 중에  가장 많으 쓰이는 것이 바로 가상 복사 생성자이다.

포인터가 실제로 가리키는 타입에 맞게 객체를 복사하고 싶다! 할 때 쓰인다.

가상 복사 생성자는 다음과 같이 쓸 수 있는데..

class Base{

public:

    virtural Base * clone() = 0;

};


class DerivedA : public Base{

public:

    virtual DerivedA * clone() { return new DerivedA(*this); }

};


class DerivedB : public Base{

public:

    virtual DerivedB * clone() { return new DerivedB(*this); }

};

가상 복사 생성자는 return형을 이용한 오버라이딩 방법이다. 

이 clone 가상함수가 바로 가상 생성자이다. 구현 방법은 간단하다. 클래스 각각의 진짜 복사 생성자를 호출하는 것으로 끝이다.

진짜 복사 생성자가 얕은 복사(Shallow copy)이면 가상 복사 생성자도 그렇고, Deep copy여도 그렇고, reference counting이나 기록 시점 복사(copy on write)라고 해도 가상 복사 생성자도 그렇게 행동한다. 

즉, 일관성 있는 동작을 유지한다는 것이다.

만약 이런 가상 복사 생성자를 쓰지 않는다면 어떨까? 그렇다면 다음과 같이 복사를 수행할 때,


class C

{

public:

C(const C& rhs);

private:

list<C*> components;

};


C::C(const C& rhs)

{

for (list<C*>::iterator it=rhs.components.begin(); it != rhs.components.end(); ++it)

{

components.push_back((*it)->clone());

}

}

(파란 부분) 이렇게 하면 간단해 질 것을

dynamic_cast나 RTTI를 활용하거나..해서 형을 알아낸 후 일일이 각 형에 맞는 복사 생성자를 호출해 줘야 할 것이다.

목적은 단지 포인터가 실제로 가리키는 타입에 맞게 객체를 복사하고 싶은 것인데, 이에 딱 맞는 해결책이 바로 가상 복사 생성자이다.


그리고 비 멤버 함수를 가상 함수처럼 만드는 방법이 있는데, 

만약 Text와 Graphic 클래스에 대해 따로 동작하는 출력 연산자를 구현하고 싶다고 치면, operator<<를 오버로딩을 한다고 해도 좌변 인자로 받게 되어 t << cout; 같은 식으로 이상하게 써야 한다. 이는 출력 연산자의 통상적인 쓰임새와 반대라 좋지 않다. 일관성 없는 행동이다.

이것에 대한 해결법은 다음과 같이 원하는 일을 하는 가상 함수를 멤버 함수로 만들어 두고, 비 가상 함수에서 이를 호출하도록 하게 하면 된다. 함수가 한 번 더 호출되는 비용은 inline 키워드를 넣는 것으로 해결 가능하다.

class Base{

public:

virtual ostream& print(ostream& s) const = 0;

...

};


class Text : public Base

{

public:

virtual ostream& print(ostream& s) const;

...

};


class Graphic : public Base

{

virtual ostream& print(ostream& s) const;

...

};


inline ostream& operator<<(ostream& s, const Base& b)

{

return b.print(s);

}


반응형