c++ - 将参数传递给构造函数和成员函数时移动或复制
问题描述
以下是我的典型代码示例。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 引用,以避免在传递对象时重复复制。
- 简单类型 - 按值传递;类和结构 - 通过 const 引用传递(或当我需要修改它们时的简单引用)
- 强制编译器创建
default
移动构造函数和移动赋值,以便它能够做它的奇特魔法,同时它允许避免编写无聊ctor() : m_v1(std::move(v1)), m_v2(std::move(v2)), m_v3(std::move(v3)) {}
的 . - 如果表现不佳,请使用 libc 和原始指针,然后将其包装在课堂上并写下注释。
我有一种强烈的感觉,根据经验法则是有缺陷的,而且根本不正确。
看完cppreference、Scott Mayers、C++标准、Stroustrup等,感觉是:“是的,这里的每一个字我都看懂了,但还是没有任何意义”。我唯一理解的就是move语义当我的类包含不可复制的类型(如std::mutex
和)时才有意义std::unique_ptr
。
我见过很多代码,人们通过值传递复杂的对象,比如大字符串、向量和自定义类——我相信这就是移动语义发生的地方,但是,再次,你如何通过移动将对象传递给函数?如果我是正确的,它将使对象处于“某种空状态”,使其无法使用。
所以,问题是: - 我如何正确地决定传递值和传递引用?- 我是否需要同时提供复制和移动构造函数?- 我需要显式编写移动和复制构造函数吗?我可以用= default
吗?我的类主要是 POD 对象,因此不涉及复杂的登录。- 调试时,我总是可以在自己的类的构造函数中编写std::cout << "move\n";
或编写std::cout << "copy\n";
,但是我怎么知道来自的类会发生什么stdlib
?
PS它可能看起来像是绝望的呐喊(确实如此),而不是一个有效的SO问题。我根本不知道如何比这更好地表述我的问题。
解决方案
如果是原始类型,则按值传递。参考地点获胜。
如果您不打算存储它的副本,请通过值或
const&
.如果您想存储它的副本,并且移动非常便宜且复制成本适中,请按价值传递。
如果某物的移动成本适中,并且是接收器参数,请考虑通过右值引用传递。用户将被迫
std::move
。考虑为调用者提供一种方法,以便在高度通用的代码中将构造嵌入到字段中,或者在您需要每一盎司性能的地方
0/3/5 规则描述了您应该如何处理复制分配/构造/销毁。理想情况下,您遵循 0 规则;复制/移动/破坏=default
除了资源管理类型之外的所有内容。如果要实现任何复制/移动/破坏,则需要实现,=default
或=delete
5 个中的每一个。
如果您只将 1 个参数传递给 setter,请考虑同时编写 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_back
或emplace
在标准容器中(它们使用放置::new
来构造对象,转发传入的对象)。
Emplace 构造甚至允许直接构造而无需使用operator T()
正确设置的对象进行移动。但这超出了这个问题的范围。
推荐阅读
- javascript - 在函数参数中使用等待
- python - 编程挑战:这个算法(与数论相关)是如何工作的?
- r - 在 R 中使用 plm 进行样本外预测的具有三个指标的固定效应模型
- java - Junit实现以编程方式禁用测试功能的执行条件
- mysql - sql查询显示尚未订购/出现在发票上的产品
- java - 显示带变焦的前后摄像头
- xamarin - Xamarin Forms RelativeLayout 嵌套在 StackLayout 中无法设置高度
- terminal - 如何在 Eclipse RCP 产品的工具栏中隐藏终端图标?
- ruby-on-rails - 在 rails 的生产模式下,我的工人在模型中看不到任何东西
- ruby-on-rails - JRuby Rails 控制台挂在“切换到检查模式”上