4

假设我有一个类,我打算将它作为可实例化的类直接公开给程序员:

class Base
{
public:
    Base(std::string text) : m_text(std::move(text)) {}
private:
    std::string m_text;
};

到现在为止还挺好。这里不需要右值构造函数。现在,在未来的某个时候,我决定扩展 Base:

class Derived : public Base
{
public:
    Derived(const std::string &text) : Base(text) {}
};

这让我很烦恼:我不能在 Derived 中按值获取字符串,因为这就是 Base 已经在做的事情——我最终会得到 2 个副本和 1 个移动。此处的 const-reference 构造函数还会对右值执行不必要的复制。

问题:如何在不添加更多构造函数的情况下仅复制 + 移动一次(就像 Base 中的简单构造函数一样)?

4

3 回答 3

8

您不能只复制和移动一次,除非您更改类的设计并将它们的构造函数转换为(可能受 SFINAE 约束的)模板转发构造函数(Yakk 的答案显示了如何)。

虽然这样做可以在提供右值时只执行一次移动而不执行复制,而在提供左值时执行一次复制而不执行任何移动,但在大多数情况下这是一种矫枉过正的做法。

作为基于模板的转发构造函数的替代方案,您可以在基类和派生类中提供两个构造函数:一个用于右值引用,一个用于左值引用const。但同样,这在大多数情况下是不必要的复杂化(并且当参数数量增加时不能很好地扩展,因为所需的构造函数的数量会成倍增加)。

移动 anstd::string与复制指针和整数一样快(此处忽略 SSO 优化),除非您有真实证据证明这是阻止您的应用程序满足其性能要求的瓶颈(难以置信),否则您不应该为此烦恼.

因此,只需让您的Derived构造函数无条件地按值获取参数,并在将其传递给基类的构造函数时将其移动:

class Derived : public Base
{
public:
    Derived(std::string text) : Base(std::move(text)) { }
};

如果您希望(或接受)Derive继承所有Base构造函数,另一种选择是利用 C++11 的继承构造函数,如下所示:

class Derived : public Base
{
public:
    using Base::Base;
//  ^^^^^^^^^^^^^^^^^
};
于 2013-05-23T19:52:43.880 回答
3

使用 SFINAE 构造函数进行完美转发:

class Derived : public Base
{
public:
  template<
    typename T,
    typename=typename std::enable_if<
      std::is_constructible<std::string, T&&>::value
    >::type
  >
  Derived(T&& text) : Base(std::forward<T>(text)) {}
};

一个缺点(除了上述构造的疯狂之外)是这将参与重载决议到一个过度贪婪的程度:它几乎完美匹配任何可以转换为 a 的参数std::string。因此,如果您有另一个使用 a 的构造函数,char const*如果您传入一个char const*变量,它可能会被忽略,因为T&&可能匹配得更好。

如果您的DerivedBaseoperator std::string...

于 2013-05-23T19:53:47.763 回答
3

更改Derived为以下内容:

class Derived : public Base
{
public:
    using Base::Base;
};

现在DerivedBase.

有关更多信息,请参阅在 C++0x 中转发所有构造函数。

于 2013-05-23T20:00:01.390 回答