C++:将对象传递给 class 构造函数,它是如何存储的?

C++: Passing object to class constructor, how is it stored?

考虑以下用 C++ 实现的简单 class 示例。

foo.hpp

#include <vector>
class Foo {
private:
    std::vector<double> X;
public:
    Foo() = default;
    ~Foo() = default;
    Foo(std::vector<double>&);
};

foo.cpp

#include "Foo.hpp"
Foo::Foo(std::vector<double>& X):
        X(X)
{}

在这种情况下,向量 X 通过引用传递给 class Foo 的构造函数。不过,我开始怀疑实现中的操作 X(X) 是否复制并将其粘贴到 class.

的成员 X 中

这是哪个?

是的,数据成员X将从构造函数参数X复制初始化。

如果将数据成员X 声明为引用,则不会发生复制操作。例如

class Foo {
private:
    std::vector<double>& X;
public:
    ~Foo() = default;
    Foo(std::vector<double>&);
};

Foo::Foo(std::vector<double>& X):
        X(X)
{}

然后

std::vector<double> v;
Foo f(v); // no copies; f.X refers to v

放心。成员的类型是 vector<double>,因此编译器将在 vector<double> class 中查找与提供的参数类型匹配的构造函数重载 (vector<double>&).

它将找到的最佳匹配是 const vector<double>& 重载 - 复制构造函数。

作为替代方案,您可以为 class:

创建一个带有右值引用的构造函数
#include <vector>
class Foo {
private:
    std::vector<double> X;
public:
    Foo() = default;
    ~Foo() = default;
    Foo(const std::vector<double>&);
    Foo(std::vector<double>&&);
};

那么构造器会这样实现:

Foo::Foo(std::vector<double>&& X_) : X(std::move(X_)) {}

调用此构造函数时不进行复制。

在您的示例中,X(Foo class 的成员)是 copy-constructed。防止复制的一种解决方案是将 X 定义为 Foo class 中的 lvalue reference,尽管它不是适用于所有情况的通用解决方案,并且它可能很危险(导致悬空引用)如果当 Foo 还活着时,原始 X 的范围就完成了!总而言之,根据您的应用程序,您可以选择以下选项之一

  1. 对X使用lvalue reference,如果每个可能的Foo对象的范围低于传递的对象(X):

    foo.hpp

    #include <vector>
    class Foo {
    private:
        std::vector<double> &X;
    public:
        Foo() = default;
        ~Foo() = default;
        Foo(std::vector<double>&);
    };
    

    foo.cpp

    #include "Foo.hpp"
    Foo::Foo(std::vector<double>& X):
            X(X) // keep the lvalue refrence to the passed Object
    {}
    

然后

std::vector<double> v;
Foo f(v); // no copies; f.X refers to v
// Foo function calls including f.X should be finished before destruction of the v Object
  1. 将 X 转换为 rvalue reference (std::move) 并将其移动到 Foo class 的成员 X,如果 X 内容不应该通过Foo 构造函数调用后传递的对象名称:

    foo.hpp

     #include <vector>
     class Foo {
     private:
         std::vector<double> X;
     public:
         Foo() = default;
         ~Foo() = default;
         Foo(std::vector<double>&&);
     };
    

    foo.cpp

     #include "Foo.hpp"
     Foo::Foo(std::vector<double>&& X):
             X(std::move(X)) // move constructor of std::vector<double> is called in this line
     {}
    

然后

std::vector<double> v = {1.0, 2.0, 3.0}; 
Foo f(std::move(v)); // no copies; f.X use resource of v, but v is empty now
// v is empty now
  1. 最后一个方法和你的实现完全一样。在这种情况下: foo.hpp

     #include <vector>
     class Foo {
     private:
         std::vector<double> X;
     public:
         Foo() = default;
         ~Foo() = default;
         Foo(const std::vector<double>&);
     };
    

    foo.cpp

     #include "Foo.hpp"
     Foo::Foo(const std::vector<double>& X): // using const is better! in this case
             X(X) // copy constructor of std::vector<double> is called in this line
     {}
    

然后

std::vector<double> v = {1.0, 2.0, 3.0};
Foo f(v); // do copies; f.X copy the resource of v, and v is still valid

话虽如此,所有这些方法根据情况都有自己的应用。为了清楚起见,只需简单地应用此优先级规则:
1- 通过 rvalue reference,结果为成员 move constructor。 (例如,当你想将一个对象传递给 std::thread 构造函数时,还有所有权,也就是资源,等等资源的生命周期!)。
2- 当传递的对象生命周期(在这些示例中为 v)大于引用生命周期时,传递 lvalue reference 并保留它。
3-传递const lvalue reference并调用copy constructor(在这些示例中为std::vectorcopy constructor),当最后的选择不适用时,要么是由于进一步使用传递的对象或由于传递的对象的 lifetime