与第三方库的钻石继承
Diamond inheritance with a third party library
我在 C++ 中有一个像这样的经典菱形问题
A
/ \
B C
\ /
D
我知道这通常可以通过让 B 和 C 虚拟继承 A 来解决。
但我的问题是 类 A 和 B 来自我无法编辑的第三方库,并且 B 对 A 的继承未标记为虚拟。
有办法解决吗?
感谢您的帮助 ;-)
解决这个问题的一个简单方法是引入一个Adapterclass。这样,层次结构就变成了
A
/
B AdapterC
\ /
D
AdapterC 的代码看起来像
class AdapterC
{
public:
explicit AdapterC(C c) : c(std::move(c)) {}
operator C& () { return c; } //Maybe this should be explicit too...
/** Interface of C that you want to expose to D, e.g.
int doSomething(double d) { return c.doSomething(d); }
**/
private:
C c;
};
俗话说,“All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections”。当然,编写和维护这个 Adapter 的工作量可能会很大。因此,我认为评论您的问题的人可能是正确的,您应该重新审视您的设计。
关键设计问题
如果您不能将库中 A 的继承更改为 virtual
,则无法在顶部制作具有单个 A 元素的菱形。该标准明确允许混合使用同一基础的虚拟和非虚拟继承class:
10.1/6: for an object c of class type C, a single subobject of type V is shared by every base subobject of c that has a virtual base
class of type V. (...).
10.1/7: A class can have both virtual and non-virtual base classes of a given type.
示例:
namespace mylib { // namesape just to higlight the boundaries of the library
struct Person { // A
static int counter;
int id;
Person() : id(++counter) {}
void whoami() { cout << "I'm "<<id<<endl; }
}; //A
struct Friend: Person {}; //B -> A
int Person::counter=0;
}
struct Employee : virtual mylib::Person {}; // C->A
struct Colleague : Employee, mylib::Friend {}; // D->(B,c)
...
mylib::Friend p1; // ok !
p1.whoami();
Employee p2; // ok !
p2.whoami();
Colleague p3; // Attention: No diamond !
//p3.whoami(); // ouch !! not allowed: no diamond so for which base
// object has the function to be called ?
p3.Employee::whoami(); // first occurrence of A
p3.mylib::Friend::whoami(); // second second occurrence of A
替代设计
由于您无法干预外部库,因此您必须以不同的方式组织事情。但不管你怎么做,都将是汗水和泪水。
您可以通过使用 A 的组合(在我的示例中为 Person
)来定义 C(在我的示例中为 Employee
)。 A 子对象将被创建或在特殊情况下从另一个对象接管。您需要努力复制 A 的接口,将调用转发给 A 子对象。
总体思路如下:
class Employee {
mylib::Person *a;
bool owna;
protected:
Employee (mylib::Person& x) : a(&x), owna(false) { } // use existing A
public:
Employee () : a(new mylib::Person), owna(true) { } // create A subobject
~Employee () { if (owna) delete a; }
void whoami() { a->whoami(); } // A - fowarding
};
如果这样做,则可以在构造函数中使用一个技巧来定义具有多重继承的 D:
struct Colleague : mylib::Friend, Employee {
Colleague () : mylib::Friend(), Employee(*static_cast<Person*>(this)) {};
using Friend::whoami;
};
唯一的问题是 A 接口的成员函数的歧义(如上文所述,已在 C 中提供)。因此,您必须使用 using 子句告诉您,对于 A,您通过 B 而不是通过 C。
最后,你可以使用这个:
Employee p2;
p2.whoami();
Colleague p3; // Artifical diamond !
p3.whoami(); // YES !!
p3.Employee::whoami(); // first occurence of A
p3.mylib::Friend::whoami(); // second second occurence of A
// all whoami refer to the same A !!!
效果很好:Online demo
结论
所以是的,可以解决这个问题,但这非常棘手。正如我所说:这将是汗水和泪水。
例如,您可以将 Colleague
转换为 Person
。但是对于 Employee
,您需要提供转换运算符。您必须在 Employee
中实施 3/5 规则,并且您必须处理所有可能出错的事情(分配失败等)。这不会是小菜一碟。
所以重新考虑您的设计确实值得,正如评论中建议的 Lightness Races in Orbit :-)
我在 C++ 中有一个像这样的经典菱形问题
A
/ \
B C
\ /
D
我知道这通常可以通过让 B 和 C 虚拟继承 A 来解决。
但我的问题是 类 A 和 B 来自我无法编辑的第三方库,并且 B 对 A 的继承未标记为虚拟。
有办法解决吗?
感谢您的帮助 ;-)
解决这个问题的一个简单方法是引入一个Adapterclass。这样,层次结构就变成了
A
/
B AdapterC
\ /
D
AdapterC 的代码看起来像
class AdapterC
{
public:
explicit AdapterC(C c) : c(std::move(c)) {}
operator C& () { return c; } //Maybe this should be explicit too...
/** Interface of C that you want to expose to D, e.g.
int doSomething(double d) { return c.doSomething(d); }
**/
private:
C c;
};
俗话说,“All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections”。当然,编写和维护这个 Adapter 的工作量可能会很大。因此,我认为评论您的问题的人可能是正确的,您应该重新审视您的设计。
关键设计问题
如果您不能将库中 A 的继承更改为 virtual
,则无法在顶部制作具有单个 A 元素的菱形。该标准明确允许混合使用同一基础的虚拟和非虚拟继承class:
10.1/6: for an object c of class type C, a single subobject of type V is shared by every base subobject of c that has a virtual base class of type V. (...).
10.1/7: A class can have both virtual and non-virtual base classes of a given type.
示例:
namespace mylib { // namesape just to higlight the boundaries of the library
struct Person { // A
static int counter;
int id;
Person() : id(++counter) {}
void whoami() { cout << "I'm "<<id<<endl; }
}; //A
struct Friend: Person {}; //B -> A
int Person::counter=0;
}
struct Employee : virtual mylib::Person {}; // C->A
struct Colleague : Employee, mylib::Friend {}; // D->(B,c)
...
mylib::Friend p1; // ok !
p1.whoami();
Employee p2; // ok !
p2.whoami();
Colleague p3; // Attention: No diamond !
//p3.whoami(); // ouch !! not allowed: no diamond so for which base
// object has the function to be called ?
p3.Employee::whoami(); // first occurrence of A
p3.mylib::Friend::whoami(); // second second occurrence of A
替代设计
由于您无法干预外部库,因此您必须以不同的方式组织事情。但不管你怎么做,都将是汗水和泪水。
您可以通过使用 A 的组合(在我的示例中为 Person
)来定义 C(在我的示例中为 Employee
)。 A 子对象将被创建或在特殊情况下从另一个对象接管。您需要努力复制 A 的接口,将调用转发给 A 子对象。
总体思路如下:
class Employee {
mylib::Person *a;
bool owna;
protected:
Employee (mylib::Person& x) : a(&x), owna(false) { } // use existing A
public:
Employee () : a(new mylib::Person), owna(true) { } // create A subobject
~Employee () { if (owna) delete a; }
void whoami() { a->whoami(); } // A - fowarding
};
如果这样做,则可以在构造函数中使用一个技巧来定义具有多重继承的 D:
struct Colleague : mylib::Friend, Employee {
Colleague () : mylib::Friend(), Employee(*static_cast<Person*>(this)) {};
using Friend::whoami;
};
唯一的问题是 A 接口的成员函数的歧义(如上文所述,已在 C 中提供)。因此,您必须使用 using 子句告诉您,对于 A,您通过 B 而不是通过 C。
最后,你可以使用这个:
Employee p2;
p2.whoami();
Colleague p3; // Artifical diamond !
p3.whoami(); // YES !!
p3.Employee::whoami(); // first occurence of A
p3.mylib::Friend::whoami(); // second second occurence of A
// all whoami refer to the same A !!!
效果很好:Online demo
结论
所以是的,可以解决这个问题,但这非常棘手。正如我所说:这将是汗水和泪水。
例如,您可以将 Colleague
转换为 Person
。但是对于 Employee
,您需要提供转换运算符。您必须在 Employee
中实施 3/5 规则,并且您必须处理所有可能出错的事情(分配失败等)。这不会是小菜一碟。
所以重新考虑您的设计确实值得,正如评论中建议的 Lightness Races in Orbit :-)