【问题标题】:C++: Passing object to class constructor, how is it stored?C ++:将对象传递给类构造函数,它是如何存储的?
【发布时间】:2022-01-11 09:14:47
【问题描述】:

考虑以下 C++ 中的简单类实现示例。

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 通过引用传递给类 Foo 的构造函数。不过,我开始怀疑实现中的操作 X(X) 是否会复制并将其“粘贴”到类的成员 X 中。

这是什么?

【问题讨论】:

  • 它调用向量的复制构造函数。 (您只有一份)。
  • 首先,您希望调用者保留他们的向量版本吗?如果没有,则创建一个移动构造函数,Foo(std::vector&lt;double&gt;&amp;&amp; X)std::move() 到成员 X 中。

标签: c++ class constructor reference


【解决方案1】:

是的,数据成员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

【讨论】:

  • 太好了,这就是我要找的。谢谢
  • @G.Gare 注意生命周期,如果被引用的对象被销毁,引用就会变得悬空。您可能需要自己实现赋值运算符。
  • 愚蠢的问题:在你的代码中,如果类实例 f 被销毁,向量 v 会发生什么?
  • @G.Gare:什么都没有。请注意:原始引用和原始指针被视为“非拥有”。这里有一个线索:github.com/isocpp/CppCoreGuidelines/blob/master/… 所以由 确保vf 使用它时保持活动状态,并清理它(这将在此自动发生案例:堆栈变量)
【解决方案2】:

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

  1. 使用lvalue reference对X,如果每个可能的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. 如果在调用 Foo 构造函数后 X 内容不应该通过传递的对象名称使用,则将 X 转换为 rvalue reference (std::move) 并将其移动到 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(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- 传递 lvalue reference 并保留它,当传递的对象生命周期(在这些示例中为 v)大于引用生命周期时。
3- 传递const lvalue reference 并让copy constructor 被调用(在这些示例中为std::vectorcopy constructor),当最后的选择不适用时,由于进一步使用传递的对象或由于@ 987654341@传递的Object。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 所以它会复制X?这是我想避免的事情
    • @G.Gare 如果没有副本,你想要什么?
    • 参考。矢量 X 在我的应用程序中很大,所以最好不要在代码周围复制它
    • 那么你应该将成员声明为引用(或指针)。该成员将使用引用参数的地址进行初始化。
    • @G.Gare -- 你认为什么是海量数据?向量将其数据存储在连续内存中,而不是像std::list 那样在整个堆中碎片化,甚至不会像std::setstd::map 这样的关联容器。所以也许你的担忧不应该是担忧?
    【解决方案4】:

    作为替代方案,您可以为类创建一个带有右值引用的构造函数:

    #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_)) {}
    

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

    【讨论】:

    • 您知道“5/7/0 规则”吗?这应该如何应用于提议的课程?
    • 具有右值引用的构造函数不是Foo(Foo&amp;&amp;)。所以它不是真正的移动构造函数,而是一个接受 r 值引用的构造函数。应该改写解决方案。
    猜你喜欢
    • 2014-06-04
    • 2016-10-12
    • 1970-01-01
    • 2015-12-13
    • 2017-03-17
    • 1970-01-01
    • 2021-05-08
    相关资源
    最近更新 更多