将参数传递给构造函数和成员函数时移动或复制
move or copy when passing arguments to the constructor and member functions
以下是我的典型代码示例。 A 有很多看起来像这样的对象:
struct Config
{
Config();
Config(const std::string& cType, const std::string& nType); //additional variables omitted
Config(Config&&) = default;
Config& operator=(Config&&) = default;
bool operator==(const Config& c) const;
bool operator!=(const Config& c) const;
void doSomething(const std::string& str);
bool doAnotherThing(const MyOtherObject& obj);
void doYetAnotherThing(int value1, unsigned long value2, const std::string& value3, MyEnums::Seasons value4, const std::vector<MySecondObject>& value5);
std::string m_controllerType;
std::string m_networkType;
//...
};
//...
Config::Config(const std::string& cType, const std::string& nType) :
m_controllerType(cType),
m_networkType(nType)
{
}
我的动机和对该主题的一般理解:
- 在构造函数和方法中使用 const 引用以避免在传递对象时进行双重复制。
- 简单类型——按值传递; classes 和结构 - 通过 const 引用传递(或当我需要修改它们时的简单引用)
- 强制编译器创建
default
移动构造函数和移动赋值,这样它就可以施展魔法,同时避免编写无聊的代码 ctor() : m_v1(std::move(v1)), m_v2(std::move(v2)), m_v3(std::move(v3)) {}
.
- 如果表现不佳,使用libc和原始指针,然后将其包装在class并写评论。
我有一种强烈的感觉,根据经验法则是有缺陷的,而且根本不正确。
在阅读了 cppreference、Scott Mayers、C++ standard、Stroustrup 等之后,我觉得:“是的,我理解这里的每一个词,但它仍然没有任何意义”。我唯一的王者可以理解的是,当我的 class 包含不可复制的类型时,移动语义才有意义,例如 std::mutex
和 std::unique_ptr
.
我见过很多代码,人们在其中按值传递复杂对象,例如大字符串、向量和自定义 classes - 我相信这是移动语义发生的地方,但是,再一次,如何你通过移动将一个对象传递给一个函数?如果我是正确的,它会在 "kind-of-null-state" 中留下一个对象,使其无法使用。
所以,问题是:
- 我如何正确地决定按值传递和按引用传递?
- 我需要同时提供复制和移动构造函数吗?
- 我是否需要显式编写移动和复制构造函数?我可以使用 = default
吗?我的 classes 主要是 POD 对象,因此不涉及复杂的登录。
- 调试时,我总是可以在自己的 classes 的构造函数中编写 std::cout << "move\n";
或 std::cout << "copy\n";
,但我怎么知道 stdlib
中的 classes 会发生什么]?
P.S。它可能看起来像是绝望的呼喊(确实如此),而不是一个有效的 SO 问题。我根本不知道如何更好地表述我的问题。
如果是原始类型,按值传递。参考地点获胜。
如果您不打算存储它的副本,按值传递或const&
。
如果你想存储它的副本,并且移动非常便宜,复制成本适中,请通过 value.
如果某物的移动成本适中,并且是接收器参数,请考虑通过右值引用传递。用户将被迫 std::move
.
考虑为调用者提供一种方法,以将构造置于高度通用代码的字段中,或者您需要每一盎司性能的地方
The Rule of 0/3/5 描述了您应该如何处理副本 assign/construct/destroy。理想情况下,您遵循 0 的规则; copy/move/destruct 是所有 =default
除了资源管理类型。如果要实施 any of copy/move/destruct,则需要实施 =default
或 =delete
5.[=23= 中的每一个]
如果您只对 setter 使用 1 个参数,请考虑同时编写 setter 的 &&
和 const&
版本。或者只是暴露底层对象。移动赋值有时会重用存储并且效率很高。
安放看起来像这样:
struct emplace_tag {};
struct wrap_foo {
template<class...Ts>
wrap_foo(emplace_tag, Ts&&...ts):
foo( std::forward<Ts>(ts)... )
{}
template<class T0, class...Ts>
wrap_foo(emplace_tag, std::initializer_list<T0> il, Ts&&...ts):
foo( il, std::forward<Ts>(ts)... )
{}
private:
Foo foo;
};
还有许多其他方法可以允许 "emplace" 构建。也请参阅标准容器中的 emplace_back
或 emplace
(它们使用放置 ::new
构造对象,转发传入的对象)。
Emplace 构造甚至允许直接构造,甚至无需使用正确设置 operator T()
的对象进行移动。但这超出了这个问题的范围。
以下是我的典型代码示例。 A 有很多看起来像这样的对象:
struct Config
{
Config();
Config(const std::string& cType, const std::string& nType); //additional variables omitted
Config(Config&&) = default;
Config& operator=(Config&&) = default;
bool operator==(const Config& c) const;
bool operator!=(const Config& c) const;
void doSomething(const std::string& str);
bool doAnotherThing(const MyOtherObject& obj);
void doYetAnotherThing(int value1, unsigned long value2, const std::string& value3, MyEnums::Seasons value4, const std::vector<MySecondObject>& value5);
std::string m_controllerType;
std::string m_networkType;
//...
};
//...
Config::Config(const std::string& cType, const std::string& nType) :
m_controllerType(cType),
m_networkType(nType)
{
}
我的动机和对该主题的一般理解:
- 在构造函数和方法中使用 const 引用以避免在传递对象时进行双重复制。
- 简单类型——按值传递; classes 和结构 - 通过 const 引用传递(或当我需要修改它们时的简单引用)
- 强制编译器创建
default
移动构造函数和移动赋值,这样它就可以施展魔法,同时避免编写无聊的代码ctor() : m_v1(std::move(v1)), m_v2(std::move(v2)), m_v3(std::move(v3)) {}
. - 如果表现不佳,使用libc和原始指针,然后将其包装在class并写评论。
我有一种强烈的感觉,根据经验法则是有缺陷的,而且根本不正确。
在阅读了 cppreference、Scott Mayers、C++ standard、Stroustrup 等之后,我觉得:“是的,我理解这里的每一个词,但它仍然没有任何意义”。我唯一的王者可以理解的是,当我的 class 包含不可复制的类型时,移动语义才有意义,例如 std::mutex
和 std::unique_ptr
.
我见过很多代码,人们在其中按值传递复杂对象,例如大字符串、向量和自定义 classes - 我相信这是移动语义发生的地方,但是,再一次,如何你通过移动将一个对象传递给一个函数?如果我是正确的,它会在 "kind-of-null-state" 中留下一个对象,使其无法使用。
所以,问题是:
- 我如何正确地决定按值传递和按引用传递?
- 我需要同时提供复制和移动构造函数吗?
- 我是否需要显式编写移动和复制构造函数?我可以使用 = default
吗?我的 classes 主要是 POD 对象,因此不涉及复杂的登录。
- 调试时,我总是可以在自己的 classes 的构造函数中编写 std::cout << "move\n";
或 std::cout << "copy\n";
,但我怎么知道 stdlib
中的 classes 会发生什么]?
P.S。它可能看起来像是绝望的呼喊(确实如此),而不是一个有效的 SO 问题。我根本不知道如何更好地表述我的问题。
如果是原始类型,按值传递。参考地点获胜。
如果您不打算存储它的副本,按值传递或
const&
。如果你想存储它的副本,并且移动非常便宜,复制成本适中,请通过 value.
如果某物的移动成本适中,并且是接收器参数,请考虑通过右值引用传递。用户将被迫
std::move
.考虑为调用者提供一种方法,以将构造置于高度通用代码的字段中,或者您需要每一盎司性能的地方
The Rule of 0/3/5 描述了您应该如何处理副本 assign/construct/destroy。理想情况下,您遵循 0 的规则; copy/move/destruct 是所有 =default
除了资源管理类型。如果要实施 any of copy/move/destruct,则需要实施 =default
或 =delete
5.[=23= 中的每一个]
如果您只对 setter 使用 1 个参数,请考虑同时编写 setter 的 &&
和 const&
版本。或者只是暴露底层对象。移动赋值有时会重用存储并且效率很高。
安放看起来像这样:
struct emplace_tag {};
struct wrap_foo {
template<class...Ts>
wrap_foo(emplace_tag, Ts&&...ts):
foo( std::forward<Ts>(ts)... )
{}
template<class T0, class...Ts>
wrap_foo(emplace_tag, std::initializer_list<T0> il, Ts&&...ts):
foo( il, std::forward<Ts>(ts)... )
{}
private:
Foo foo;
};
还有许多其他方法可以允许 "emplace" 构建。也请参阅标准容器中的 emplace_back
或 emplace
(它们使用放置 ::new
构造对象,转发传入的对象)。
Emplace 构造甚至允许直接构造,甚至无需使用正确设置 operator T()
的对象进行移动。但这超出了这个问题的范围。