调用左值引用构造函数而不是rvalue引用构造函数

scd*_*dmb 5 c++ rvalue-reference move-semantics c++11

有这个代码:

#include <iostream>

class F {
public:
   F() = default;
   F(F&&) {
      std::cout << "F(F&&)" << std::endl;
   }
   F(F&) {
      std::cout << "F(F&)" << std::endl;
   }
};

class G {
   F f_;
public:
   G(F&& f) : f_(f) {
      std::cout << "G()" << std::endl;
   }
};

int main(){
   G g = F();
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

F(F&)
G()
Run Code Online (Sandbox Code Playgroud)

为什么在类F(F&)F(F&&)构造函数中调用构造函数而不是构造函数G?类的构造函数的参数GF&& f它是右值引用但左值参考构造函数被调用.

And*_*owl 9

为什么在G类的构造函数中调用F(F&)构造函数而不是F(F &&)构造函数?

因为f是左值.即使它绑定了一个rvalue,并且它的类型是rvalue引用F,它也是一个命名变量.这使它成为一个左值.不要忘记对象的值类别不是由其类型决定的,反之亦然.

将左值传递给函数时,只能将左值引用绑定到它.如果要仅捕获rvalues,则应按如下所示更改代码:

class G {
    F f_;
public:
    G(F&& f) : f_(std::move(f)) {
       std::cout << "G()" << std::endl;
    }
};
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用std::forward<>(),在这种情况下等效,但使您的转发 意图f更加清晰:

class G {
    F f_;
public:
    G(F&& f) : f_(std::forward<F>(f)) {
       std::cout << "G()" << std::endl;
    }
};
Run Code Online (Sandbox Code Playgroud)

现在,最后一个定义很容易扩展,因此类型的左值和右值F都可以绑定到参数f:

class G {
    F f_;
public:
    template<typename F>
    G(F&& f) : f_(std::forward<F>(f)) {
       std::cout << "G()" << std::endl;
    }
};
Run Code Online (Sandbox Code Playgroud)

例如,这允许以G这种方式构造一个实例:

F f;
G g(f); // Would not be possible with a constructor accepting only rvalues
Run Code Online (Sandbox Code Playgroud)

最后一个版本有一个警告:您的构造函数基本上也可以作为复制构造函数使用,因此您可能希望显式定义所有可能的复制构造函数以避免出现尴尬的情况:

class G {
    F f_;
public:
    template<typename F>
    G(F&& f) : f_(std::forward<F>(f)) {
       std::cout << "G()" << std::endl;
    }
    G(G const&) = default;
    G(G&); // Must be defaulted out-of-class because of the reference to non-const
};

G::G(G&) = default;
Run Code Online (Sandbox Code Playgroud)

由于非模板函数优于函数模板的实例化,因此在G从另一个G对象构造对象时将选择复制构造函数.当然,这同样适用于移动构造函数.这是一个练习.