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

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

我的动机和对该主题的一般理解:

我有一种强烈的感觉,根据经验法则是有缺陷的,而且根本不正确。

在阅读了 cppreference、Scott Mayers、C++ standard、Stroustrup 等之后,我觉得:“是的,我理解这里的每一个词,但它仍然没有任何意义”。我唯一的王者可以理解的是,当我的 class 包含不可复制的类型时,移动语义才有意义,例如 std::mutexstd::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_backemplace(它们使用放置 ::new 构造对象,转发传入的对象)。

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