【问题标题】:Setting a default constructor for one type with multiple inheritance为具有多重继承的一种类型设置默认构造函数
【发布时间】:2016-10-11 21:26:03
【问题描述】:

我有两个班级,BaseDerivedDerived 继承了 Base 的所有构造函数。另外,我有一个模板类Printer<T>,它包含对T 类型对象的引用,并有一个方法print(),它以某种方式打印一个对象。这是一个最小的插图。

class Base {
public:
    Base(int x) : x(x) {}
    int x;
};

template<typename T>
class Printer {
public:
    const T& object;

    Printer(const T& object) : object(object) {}

    void print() {
        cout << object << endl;
    }
};

class Derived : public Base {
public:
    using Base::Base;
};

std::ostream& operator<<(std::ostream& out, const Derived& d) {
    return out << d.x;
}

int main() {
    Derived d(1);

    Printer<Derived>(d).print();
}

现在我想避免直接使用Printer 并允许这样的语法:Derived d(1); d.print();。因此我尝试从Printer&lt;Derived&gt; 继承Derived

class Derived : public Base, public Printer<Derived> {
public:
    typedef Printer<Derived> MyPrinter;

    using Base::Base;

    Derived() : MyPrinter(*this) {}
};  

现在我有一个问题:Base 构造函数对Printer 一无所知,因此无法以任何方式对其进行初始化。我也不能在这里使用构造函数委托,因为Derived 中使用的构造函数实际上是从Base 继承的。

我能否以某种方式使Derived 的默认构造函数由任何其他构造函数委托,甚至是继承的构造函数?或者也许还有其他一些模式来初始化多重继承中的第二个基?

还有一件事使一切变得更加困难,那就是我无法访问Base 的代码,只能按原样使用它。

更新 在 Remy Lebeau 的回答中:Base 可以有多个我不知道的构造函数(它也是一个模板类),所以我无法实现所有这些,必须使用 using Base::Base 成语。

关于 krzaq 的回答:Printer 其实也有很多方法,不仅仅是print(),所以实现转发器类是一件麻烦事,我尽量避免。

【问题讨论】:

  • 您忘记了基类中的虚拟析构函数。
  • 不。构造函数只能由同一类的另一个构造函数委托。这也与构造函数无关。即使可以以某种方式委托构造函数,但事实是Base 的接口是刻在石头上的。就是这样。如果,如您所说,您只能“按原样”使用Base,那么您唯一能做的就是覆盖Base 中的任何虚拟方法。您不能向其中添加任何方法。结束。
  • @PaulMcKenzie 当然,真实代码中有一些,我只是在制作这个人工示例时忘记了一些。谢谢。

标签: c++ inheritance multiple-inheritance


【解决方案1】:

如果您只需要从Printer&lt;Derived&gt; 访问Derived 实例,那么您可以简单地将其强制转换:

template<typename T>
class Printer {
public:
    const T& object;

    Printer() : object(static_cast<T&>(*this)) {}

    void print() {
        cout << object << endl;
    }
};

live demo

或完全取消参考,让您的班级有资格获得EBO

template<typename T>
class Printer {
public:
    void print() {
        cout << static_cast<T&>(*this) << endl;
    }
};

live demo

如果您也不能/不想触摸 Printer,我会创建一个单独的模板 PrinterForwarder 以将 print() 调用发送到正确的打印机:

template<typename T>
class PrinterForwarder
{
public:
    void print() {
        Printer<T>(static_cast<T&>(*this)).print();
    }
};

class Derived : public Base, public PrinterForwarder<Derived> {
public:
    using Base::Base;
};

live demo

【讨论】:

  • Printer 并不总是以这种方式使用,它也可以用作独立类(如第一个示例中所示),因此 static_cast 会失败。或许我可以通过dynamic_cast&lt;T&amp;&gt; 确认真正发生继承的位置,我会试试看。
  • 好的,我认为第一个示例在某种程度上是您想要摆脱的解决方法。如果你不这样做,我想我会制作一个单独的打印机转发器,你可以从中派生,它将*this 转发到打印机。
  • @IvanSmirnov 我编辑了答案。希望这会更有帮助。
  • Printer 有几个方法,所以帮助类必须将它们全部复制。
  • @IvanSmirnov 好的。那么我认为你必须在重新声明Base的构造函数和Printer的接口之间做出选择。
【解决方案2】:

现在我有一个问题:基础构造函数对打印机一无所知,因此无法以任何方式对其进行初始化。

要执行您正在尝试的操作,您将无法再使用using Base::Base 语句。您必须更明确地了解Derived 实现的构造函数,以便它们可以根据需要分别初始化Printer 基类,例如:

class Derived : public Base, public Printer<Derived> {
public:
    typedef Printer<Derived> MyPrinter;

    Derived() : Base(0), MyPrinter(*this) {}
    Derived(int x) : Base(x), MyPrinter(*this) {}
};  

或者:

class Derived : public Base, public Printer<Derived> {
public:
    typedef Printer<Derived> MyPrinter;

    Derived() : Derived(0) {}
    Derived(int x) : Base(x), MyPrinter(*this) {}
};  

【讨论】:

  • 这似乎是不可避免的,但这不是一个选择,因为 Base 有很多 ctor,我无法重新实现它们。
  • 如果Printer 的构造函数需要输入值,那么Derived 中的所有构造函数都必须向它传递一个值,这意味着重新实现所有构造函数,如上所示。但是如果你重新设计Printer 使其构造函数中不需要输入值,就像 krzaq 显示的那样,那么你就不必再担心这个了。
【解决方案3】:

您可以创建一个可变参数模板包罗万象的构造函数,它将参数传递给 Base 并根据需要构造 Printer,像这样

class Derived: ... {
   template<typename... Args>
   Derived(Args&&... args): Base(std::forward<Args>(args)...), Printer(*this) {}
}

【讨论】:

    【解决方案4】:

    我找到了一个非常简单的解决方案。让我们在 Printer 中存储指向 T 的指针,而不是引用。

    template<typename T>
    class Printer {
    public:
        const T* objectPtr;
    
        Printer() : objectPtr(nullptr) {}
        Printer(const T& object) : objectPtr(&object) {}
    
        void print() {
            if (objectPtr) {
                cout << *objectPtr << endl;
            } else {
                cout << static_cast<const T&>(*this) << endl;
            }
        }
    };
    

    看起来不太安全,但将 Printer() ctor 设为私有似乎可以达成交易。

    【讨论】:

    • 如果没有虚函数和强制转换,会不会调用错误的操作符(对于打印机,而不是对于T)?
    • 当然可以,现在修好了。这里当然需要转换为T
    • 另请注意,在这种情况下,复制/移动 ctor 值得删除,否则当您复制打印机部件时会得到一些有趣的东西
    • 你又是对的。尽管删除它们会导致派生类不可复制/可移动,这是不希望的。也许更合适的解决方案是在创建时存储this 指针并将其传递给复制/移动ctor。我还在尝试。
    猜你喜欢
    • 2011-05-20
    • 1970-01-01
    • 1970-01-01
    • 2017-10-04
    • 2016-12-05
    • 2016-03-24
    • 2011-04-12
    • 1970-01-01
    相关资源
    最近更新 更多