为什么派生类中复制/移动函数的"正常"实现的行为会有所不同,具体取决于它们的定义方式?

Kno*_*abe 1 c++ constructor-overloading c++11 universal-reference

当派生类复制和移动函数调用它们的基类版本时,我对我所看到的行为感到困惑.

我有一个基类与各种构造函数,告诉我什么时候被调用:

#include <iostream>

class Base {
public:     
  Base() {}

  template<typename T>
  Base(T&&) { std::cout << "URef ctor\n"; }

  Base(const Base&) { std::cout << "Copy ctor\n"; }

  Base(Base& rhs): Base(const_cast<const Base&>(rhs))
  { std::cout << "  (from non-const copy ctor)\n"; }

  Base(Base&&) { std::cout << "Move ctor\n"; }

  Base(const Base&& rhs): Base(rhs)
  { std::cout << "  (from const move ctor)\n"; }
};
Run Code Online (Sandbox Code Playgroud)

对于具有编译器生成的复制和移动操作的派生类

class Derived: public Base {};
Run Code Online (Sandbox Code Playgroud)

和这个测试代码,

int main()                              
{                                      
  Derived d;                         
  Derived copyNCLValue(d);          
  Derived copyNCRvalue(std::move(d)); 

  const Derived cd;
  Derived copyCLValue(cd);      
  Derived copyCRvalue(std::move(cd));    
}
Run Code Online (Sandbox Code Playgroud)

gcc 4.8.1产生这个输出:

Copy ctor
Move ctor
Copy ctor
Copy ctor
Run Code Online (Sandbox Code Playgroud)

这让我感到惊讶.我期望调用通用引用的基类构造函数被调用,因为它可以被实例化以在派生对象上创建完全匹配,这可能是从派生类的函数传递的.基类复制和移动函数需要派生到基础的转换.

如果我更改派生类以自己声明副本并移动函数,但是为它们提供默认实现,

class Derived: public Base {
public:
  Derived(){}

  Derived(const Derived& rhs) = default;
  Derived(Derived&& rhs) = default;     
};
Run Code Online (Sandbox Code Playgroud)

gcc产生相同的输出.但是如果我自己用我认为默认的实现来编写函数,

class Derived: public Base {
public:
  Derived(){}

  Derived(const Derived& rhs): Base(rhs) {}
  Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
Run Code Online (Sandbox Code Playgroud)

我得到了我原先预期的输出:

URef ctor
URef ctor
URef ctor
URef ctor
Run Code Online (Sandbox Code Playgroud)

我期望在每种​​情况下获得相同的输出.这是gcc中的一个错误,还是有些东西我不理解?

Dav*_*eas 6

这让我感到惊讶.我期望调用通用引用的基类构造函数被调用,因为它可以被实例化以在派生对象上创建完全匹配,这可能是从派生类的函数传递的.基类复制和移动函数需要派生到基础的转换.

Derived copyCRvalue(std::move(cd));不会.编译器会看到真正含义的行,Derived copyCRvalue(static_cast<const Derived&&>(cd));它会尝试找到Derived与该调用匹配的构造函数.它发现两个密切相关的构造函数都隐式声明:

Derived(Derived const &); // copy constructor
Derived(Derived &&);      // move constructor
Run Code Online (Sandbox Code Playgroud)

第二个不能使用,因为rvalue-reference是一个const对象,但第一个是匹配.隐式定义的复制构造函数的定义是:

Derived(Derived const &rhs) : base(static_cast<Base const &>(rhs)) {}
Run Code Online (Sandbox Code Playgroud)

在构造函数中,它rhs是一个左值,而不是一个右值(并且模板化的构造函数不是复制构造函数).

但是如果我自己用我认为默认的实现来编写函数,

class Derived: public Base {
public:
  Derived(){}

  Derived(const Derived& rhs): Base(rhs) {}
  Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
Run Code Online (Sandbox Code Playgroud)

除了那些不是编译器将提供的定义.该标准的具体配额为12.8/15

非联合类X的隐式定义的复制/移动构造函数执行其基础和成员的成员复制/移动.

也就是说,隐式定义的构造函数将使用源的基础初始化目标的基础,并且类似地,源中具有相同成员的目标中的每个成员.