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가 별도로 가지고 있다)
이 현상을 해결하기 위해 가상 상속을 적용한다. (불필요한 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)
라고 초기화 코드를 추가 했다. 출력은 아래와 같다.
Non inheritance policy
class SomeClass : private virtual boost::noncopyable
{
public:
//< virtual 키워드는 구현 코드의 지연을 의미한다. 따라서 실제 객체를 선언할때에 판별 될 것이다.
};
참고로 C++11에서는 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 인스턴스가 A와 B의 각 이동 지정 연산자에 의해 한 번씩 두 번 이동 된 것을 나타낸다. 두 번째 이동 지정은 이미 이동 된 객체에서 가져온 것으로 첫 번째 이동 지정의 내용을 덮어 쓸 수 있습니다. Clang의 경우 -Wmultiple-move-vbase
관련 경고를 출력한다. 이 문제를 해결하는 방법은 A와 B에 대한 이동 지정 연산자를 구현하여 Base가 이동 된 것을 감지하고 두 번째 이동 지정을 생략 할 수 있습니다.