将参数传递给构造函数和成员函数时移动或复制

时间:2018-06-27 14:03:39

标签: c++ c++11 move move-semantics stdmove

以下是我的典型代码的示例。有很多看起来像这样的对象:

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的move构造函数和move赋值,这样它就可以实现幻想,同时还可以避免编写无聊的ctor() : m_v1(std::move(v1)), m_v2(std::move(v2)), m_v3(std::move(v3)) {}
  • 如果执行不佳,请使用libc和原始指针,然后将其包装在类上并编写注释。

我有一种强烈的感觉,根据经验法则是有缺陷的,根本就是错误的。

在阅读了cppreference,Scott Mayers,C ++标准,Stroustrup等之后,我感到:“是的,我理解这里的每个单词,但仍然没有任何意义。”我唯一理解的国王是当我的班级包含不可复制的类型(例如std::mutexstd::unique_ptr时,移动语义很有意义。

我看过很多代码,人们在其中按值传递复杂的对象,例如大字符串,向量和自定义类-我相信这是发生移动语义的地方,但是同样,如何将对象传递给函数通过移动?如果我是正确的话,它将使对象处于“空状态”,使其无法使用。

所以,问题是: -如何正确确定按值传递和按引用传递? -是否需要同时提供复制和移动构造函数? -是否需要显式编写移动和复制构造函数?我可以使用= default吗?我的课程主要是POD对象,因此不涉及复杂的登录。 -调试时,我总是可以在自己的类的构造函数中编写std::cout << "move\n";std::cout << "copy\n";,但是如何知道stdlib中的类会发生什么呢?

P.S。看起来这是绝望的呼唤(是),不是有效的SO问题。我根本不知道比这更好地提出问题。

1 个答案:

答案 0 :(得分:1)

  • 如果它是原始类型,则按值传递。参考文献的所在地胜出。

  • 如果您不打算存储副本,请按值或const&传递。

  • 如果要存储它的副本,并且移动非常便宜且复制成本适中,请传递 value

  • 如果某些东西的移动成本适中,并且是一个接收器参数,请考虑按右值引用传递。用户将被迫std::move

  • 考虑为调用者提供一种以高度通用的代码或在您需要每一分性能的地方将构造放入字段的方法

The Rule of 0/3/5介绍了应如何处理副本分配/构造/销毁。理想情况下,您遵循0规则;复制/移动/销毁是资源管理类型以外的所有=default。如果要实现复制/移动/销毁的任何,则需要每隔5个实现=default=delete

如果仅对setter传入1个参数,请考虑同时编写&&const&版本的setter。或者只是暴露基础对象。移动分配有时会重用存储,这很有效。

嵌入看起来像这样:

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_backemplace(它们使用放置位置::new构造对象,转发传入的对象)。

甚至使用operator T()正确设置的对象,Emplace构造甚至允许直接构造而无需移动。但这超出了这个问题的范围。