有这段代码:
#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;
}
输出结果为:
F(F&)
G()
为什么在类F(F&)
的构造函数中调用F(F&&)
构造函数而不是G
构造函数?类G
的构造函数的参数是F&& f
,它是rvalue引用,但是调用了左值引用的构造函数。
答案 0 :(得分:9)
为什么在G类的构造函数中调用F(F&amp;)构造函数而不是F(F&amp;&amp;)构造函数?
因为f
是左值。即使绑定到rvalue,并且其类型是对F
的右值引用,它也是命名变量。这使它成为一个左值。不要忘记对象的值类别不是由其类型决定的,反之亦然。
将左值传递给函数时,只能将左值引用绑定到它。如果您只想捕获右值,则应按如下所示更改代码:
class G {
F f_;
public:
G(F&& f) : f_(std::move(f)) {
std::cout << "G()" << std::endl;
}
};
或者,您可以使用std::forward<>()
,这在本例中是等效的,但是转发 f
的意图更加清晰:
class G {
F f_;
public:
G(F&& f) : f_(std::forward<F>(f)) {
std::cout << "G()" << std::endl;
}
};
现在最后一个定义很容易扩展,因此类型F
的左值和右值都可以绑定到参数f
:
class G {
F f_;
public:
template<typename F>
G(F&& f) : f_(std::forward<F>(f)) {
std::cout << "G()" << std::endl;
}
};
例如,这允许以这种方式构造G
的实例:
F f;
G g(f); // Would not be possible with a constructor accepting only rvalues
最后一个版本有一个警告:你的构造函数will basically work as a copy-constructor as well,所以你可能想要显式定义所有可能的拷贝构造函数以避免尴尬的情况:
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;
由于非模板函数优先于函数模板的实例化,因此在从另一个G
对象构造G
对象时将选择复制构造函数。当然,这同样适用于 move 构造函数。这是一个练习。