钻石继承与第三方库

时间:2016-09-05 18:04:22

标签: c++ multiple-inheritance diamond-problem

我在C ++中有这样的经典钻石问题

  A
 / \
B   C
 \ /
  D

我知道这通常可以通过使B和C从A实际上继承来解决。

但是我的问题是A类和B类来自我无法编辑的第三方库,B的A继承没有标记为虚拟。

有没有办法解决这个问题?

感谢您的帮助; - )

2 个答案:

答案 0 :(得分:0)

解决此问题的一种简单方法是引入Adapter类。这样,层次结构变为

  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”。当然,编写和维护此适配器可能需要做很多工作。因此,我认为评论你的问题的人可能是正确的,你应该重新审视你的设计。

答案 1 :(得分:0)

关键设计问题

如果您无法将库中A的继承更改为virtual,则无法使用单个A元素作为顶部的钻石。该标准明确允许混合相同基类的虚拟和非虚拟继承:

  

10.1 / 6:对于类型为C的对象c,类型为V的单个子对象 的每个基础子对象共享虚拟基础   类型为V.(...)。
   10.1 / 7:一个类可以同时拥有给定类型的虚拟和非虚拟基类。

示例:

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(我的示例中为Employee)的组合来定义C(我的示例中为Person)。 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中提供)。因此,您必须告诉使用条款,对于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在评论中所建议的那样: - )