Skip to content

C++:Inheritance

C++의 상속에 관련된 내용을 정리한다.

Virtual inheritance

다중 상속시 문제가 되는 예제는 아래와 같다.

#include <iostream>
using namespace std;

struct A {
    int ii;
    A(int i) : ii(i) { cout << "A:" << i << endl; }
    void printFromA() { cout << "ii: " << ii << std::endl; }
};

struct B : public A {
    B(int i) : A(i) { cout << "B:" << i << endl; }
    void printFromB() { cout << "ii: " << ii << std::endl; }
};

struct C : public A {
    C(int i) : A(i) { cout << "C:" << i << endl; }
    void printFromC() { cout << "ii: " << ii << std::endl; }
};

struct D : public B, public C {
    D(int b, int c) : B(b), C(c) { cout << "D:" << b << "," << c << endl; }
    // void printFromD() { cout << "ii: " << ii << std::endl; } // ambiguous error.
};

int main()
{
    D d(1, 2);
    //d.printFromA(); // ambiguous error.
    d.printFromB();
    d.printFromC();
    return 0;
}

아래와 같이 A 생성자가 두 번 호출된다. (A는 B와, C가 별도로 가지고 있다)

A:1
B:1
A:2
C:2
D:1,2
ii: 1
ii: 2

이 현상을 해결하기 위해 가상 상속을 적용한다. (불필요한 print~함수는 제거한다.)

#include <iostream>
using namespace std;

struct A {
    A(int i) { cout << "A:" << i << endl; }
};

struct B : virtual public A {
    B(int i) : A(i) { cout << "B:" << i << endl; }
};

struct C : virtual public A {
    C(int i) : A(i) { cout << "C:" << i << endl; }
};

struct D : public B, public C {
    D(int b, int c) : B(b), C(c) { cout << "D:" << b << "," << c << endl; }
};

int main()
{
    D d(1, 2);
    return 0;
}

위와 같이 하면 아래와 같은 에러가 발생된다.

test.cpp: In constructor ‘D::D(int, int)’:
test.cpp:30:32: error: no matching function for call to ‘A::A()’
     D(int b, int c) : B(b), C(c)
                                ^
test.cpp:6:5: note: candidate: A::A(int)
     A(int i)
     ^
test.cpp:6:5: note:   candidate expects 1 argument, 0 provided
test.cpp:4:8: note: candidate: A::A(const A&)
 struct A
        ^
test.cpp:4:8: note:   candidate expects 1 argument, 0 provided

virtual 키워드는 지연 바인딩을 위한 키워드이다. 따라서 B와 C에서 상속이 이루어 지지 않고 D를 통하여 상속이 이루어 진다. 따라서 아래와 같이 최종적으로 수정해야 한다.

#include <iostream>
using namespace std;

struct A {
    A(int i) { cout << "A:" << i << endl; }
};

struct B : virtual public A {
    B(int i) : A(i) { cout << "B:" << i << endl; }
};

struct C : virtual public A {
    C(int i) : A(i) { cout << "C:" << i << endl; }
};

struct D : public B, public C {
    D(int b, int c) : A(100), B(b), C(c) { cout << "D:" << b << "," << c << endl; }
};

int main()
{
    D d(1, 2);
    return 0;
}

D생성자에 A(100)라고 초기화 코드를 추가 했다. 출력은 아래와 같다.

A:100
B:1
C:2
D:1,2

Non inheritance policy

class SomeClass : private virtual boost::noncopyable
{
public:
//< virtual 키워드는 구현 코드의 지연을 의미한다. 따라서 실제 객체를 선언할때에 판별 될 것이다.
};

참고로 C++11에서는 final을 사용하면 간단히 해결된다.

class CBase final
{
...
};

Troubleshooting

virtual-move-assign

GCC에서 다음과 같은 에러가 발생할 수 있다.

defaulted move assignment for 'B' calls a non-trivial move assignment operator for virtual base 'A' [-Wvirtual-move-assign]

아래와 같은 예제가 있다.

struct Base
{
    Base() = default;
    Base& operator=(Base&&)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        return *this; 
    }
};

struct A : virtual Base {};

struct B : virtual Base {};

struct C : A, B {};

int main()
{
    C c;
    c = C();
}

출력은 아래와 같다:

Base& Base::operator=(Base&&)
Base& Base::operator=(Base&&)

단일 Base 인스턴스가 A와 B의 각 이동 지정 연산자에 의해 한 번씩 두 번 이동 된 것을 나타낸다. 두 번째 이동 지정은 이미 이동 된 객체에서 가져온 것으로 첫 번째 이동 지정의 내용을 덮어 쓸 수 있습니다. Clang의 경우 -Wmultiple-move-vbase관련 경고를 출력한다. 이 문제를 해결하는 방법은 A와 B에 대한 이동 지정 연산자를 구현하여 Base가 이동 된 것을 감지하고 두 번째 이동 지정을 생략 할 수 있습니다.

Favorite site