6

有这个代码:

#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?类的构造函数的参数GF&& f右值引用,但调用了左值引用的构造函数。

4

1 回答 1

11

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

因为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

不过,最后一个版本有一个警告:您的构造函数基本上也可以作为复制构造函数工作,因此您可能需要显式定义所有可能的复制构造函数以避免尴尬的情况:

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。当然,这同样适用于移动构造函数。这留作练习。

于 2013-03-09T19:10:14.777 回答