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 的范围就完成了!总而言之,根据您的应用程序,您可以选择以下选项之一
对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
将 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
最后一个方法和你的实现完全一样。在这种情况下:
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::vector
的copy constructor
),当最后的选择不适用时,要么是由于进一步使用传递的对象或由于传递的对象的 lifetime
。
考虑以下用 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 的范围就完成了!总而言之,根据您的应用程序,您可以选择以下选项之一
对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
将 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
最后一个方法和你的实现完全一样。在这种情况下: 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::vector
的copy constructor
),当最后的选择不适用时,要么是由于进一步使用传递的对象或由于传递的对象的 lifetime
。