与第三方库的钻石继承

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

Online demo

替代设计

由于您无法干预外部库,因此您必须以不同的方式组织事情。但不管你怎么做,都将是汗水和泪水。

您可以通过使用 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 :-)