使用字符串成员构造对象的有效方法

Efficient way to construct object with string member

假设我有一个 class ThisHasAStringMember。假设它有一个私有成员,它是一个字符串,我想有效地获取字符串值,在可能的情况下更喜欢移动而不是副本。以下两个构造函数可以实现吗?

class ThisHasAStringMember
{
public:
  // ctors
  ThisHasAStringMember(const std::string str) : m_str(str) {}
  ThisHasAStringMember(std::string &&str) : m_str(std::move(str)) {}

  // getter (no setter)
  std::string value() { return m_str; }
private:
  std::string m_str;
}

第二个构造函数中 str 参数前是否需要双符号?

这是完成此任务的正确方法吗?

起初我会注意到最好将构造函数标记为显式。

接下来最好更改解决方案中的第一个构造函数以采用 const 引用以避免复制左值:

// ctors
ThisHasAStringMember(const std::string& str) : m_str(str) {}
ThisHasAStringMember(std::string &&str) : m_str(std::move(str)) {}

从性能的角度来看,这种方法是最佳的(您将有一个复制构造函数调用左值和一个移动构造函数调用右值),但是在这种情况下每次实现两个构造函数是很无聊的。如果您有 N 个成员 - 2^N 个构造函数。

几乎没有其他选择:

  1. Signle 构造函数,您可以在其中仅按值传递参数。是的,它在 C++98 中效率低下,但在 C++11 中创建完整副本时 - 这是一个选项。

    ThisHasAStringMember(std::string str) : m_str(std::move(str)) {}

传递左值时,将调用一次复制构造函数和调用一次移动构造函数。当传递右值时,将有两个移动构造函数调用。是的,在每种情况下,您都有一个额外的移动构造函数调用。但它通常非常便宜(甚至可以被编译器优化掉)并且代码非常简单。

  1. 通过右值传递参数的单个构造函数:

    ThisHasAStringMember(std::string&& str) : m_str(std::move(str)) {}

如果你传递左值,你必须首先在调用的地方明确地复制它,例如 ThisHasAStringMember(复制(someStringVar))。 (这里copy是一种简单的模板复制方式)。而且你仍然会有一个额外的移动构造函数调用左值。对于右值,不会有开销。我个人喜欢这种方法:复制参数的所有地方都是明确的,你不会在 performance-critical 地方偶尔复制。

  1. 制作构造函数模板并使用完美转发:
template <typename String, 
    std::enable_if_t<std::is_constructible_v<std::string, String>>* = nullptr>
ThisHasAStringMember(String&& str) : m_str(std::forward<String>(str))
{}

右值和左值都没有开销,但在大多数情况下,您需要制作构造函数模板并在 header 中定义它。