多副本构造函数继承中的惊人行为

时间:2018-07-04 23:15:00

标签: c++ c++11 inheritance constructor using-declaration

从C ++ 11开始,可以有两个副本构造函数,一个带有参数T&类型的复制构造函数,一个带有const T&类型参数的复制构造函数。

在一种情况下,当构造函数在派生类中继承时,(似乎)添加第二个副本构造函数不会导致任何人被调用。当复制构造函数和复制构造函数同时存在时,它们将被模板化构造函数覆盖。

这是MWE:

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};

struct C :public A { using A::A; };

int main() {
  C c1;
  C c2(c1);
}

运行此代码,我们将看到输出

non-default ctor called
copy ctor from non-const ref

这是预期的。

但是,向struct A添加一个附加的构造函数,如下所示:

  A (const A&) { }

以某种方式导致其他副本构造函数不被调用,因此输出变为

non-default ctor called
non-default ctor called

在我的用例中,我想将所有构造函数从基类继承到派生类,包括复制构造函数和其他任何东西。但是似乎这两个副本构造函数在它们都存在时不会被继承。这是怎么回事?

1 个答案:

答案 0 :(得分:6)

来自https://en.cppreference.com/w/cpp/language/using_declaration

  

如果Base的继承构造函数之一恰好具有与Derived的复制/移动构造函数匹配的签名,则它不会阻止隐式生成Derived复制/移动构造器(然后隐藏继承的版本,类似于使用运算符=)。

所以

struct C :public A { using A::A; };

struct C :public A
{
    using A::A;
    C(const C&) = default;
    C(C&&) = default;
};

其中C(const C&) = default;

相似
C(const C& c) : A(static_cast<const A&>(c)) {}

所以

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
选择了

template构造函数,但是

struct A {
  template <typename... Args>
  A (Args&&... args)
  { std::cout << "non-default ctor called\n"; }

  A (const A&) { std::cout << "copy ctor from const ref\n"; }
  A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};

A (const A&)被选中。

您会注意到,还有一个缺陷:

  

针对C ++ 11的缺陷报告可追溯地更改继承构造函数的语义。以前,继承的构造函数声明导致将一组综合的构造函数声明注入到派生类中,这导致冗余的参数副本/移动,与某些形式的SFINAE的交互存在问题,并且在某些情况下可能无法在主要的ABI上实现。较早的编译器可能仍会实现以前的语义。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0136r1.html

有这种缺陷,您的C类应该是

struct C :public A
{
    using A::A;

    template <typename ...Ts>
    C(Ts&&... ts) : A(std::forward<Ts>(ts)...) {} // Inherited.

    C(const C&) = default;
    C(C&&) = default;
};

因此,您致电C(C& c) : A(c) {}(在模板替换之后)。