【问题标题】:What is the most efficient way to set class variable using rvalue in c++?在 C++ 中使用右值设置类变量的最有效方法是什么?
【发布时间】:2017-11-25 20:49:00
【问题描述】:

我刚开始使用 c++11 右值。我看了一些教程,但没有找到答案。

设置类变量的最佳方式(最有效的方式)是什么?下面的代码是否正确? (假设 std::string 已经定义了移动构造函数和赋值运算符)。

class StringWrapper
{
private:
    std::string str_;

public:
    StringWrapper() : str_("") {}

    void setString1(std::string&& str) {
      str_ = std::move(str);
    }

    void setString2(const std::string& str) {
      str_ = std::move(str);
    }

    // other possibility?
};

int main() {
    std::string myText("text");

    StringWrapper x1, x2;

    x1.setString?("text"); // I guess here should be setString1
    x2.setString?(myText); // I guess here should be setString2
}

我知道编译器可以优化我的代码和/或我可以使用重载函数。我只想知道什么是最好的方法。

【问题讨论】:

  • 您不需要初始化str_,因此您不需要用户定义的默认构造函数。
  • 看看标准库declares such things如何。它使用重载。顺便提一句。来自 const 引用的 std::move() 没有多大意义。在这种情况下使用str_ = str
  • setString2 并没有做它看起来正在做的事情。 str_ = std::move(str); 其中strconst std::string& 实际上不会执行移动分配。

标签: c++ c++11 rvalue-reference rvalue pass-by-rvalue-reference


【解决方案1】:

Herb Sutter's advice 这个是从标准 C++98 方法入手的:

void setString(const std::string& str) {
  str_ = str;
}

如果您需要针对右值进行优化,请添加一个采用右值引用的重载:

void setString(std::string&& str) noexcept {
  str_ = std::move(str);
}

请注意,std::string 的大多数实现都使用小字符串优化,因此如果您的字符串很小,则无论如何移动都与副本相同,您不会获得任何好处。

使用按值传递然后移动(如 Adam Hunyadi 的回答)以避免编写多个重载是很诱人的。但 Herb 指出,它不会重复使用 str_ 的任何现有容量。如果您使用左值多次调用它,它将每次分配一个新字符串。如果您有 const std::string& 过载,那么它可以重用现有容量并避免分配。

如果你真的很聪明,你可以使用一个使用完美转发的模板化设置器,但要让它完全正确实际上是相当复杂的。

【讨论】:

  • 对我来说,这是日常编程的实用方法:默认使用经过验证的const T&,它易于编写,语法开销最小,即使是经验不足的程序员也很容易理解。仅当分析显示需要重载或您有特殊用例时,才为右值引用添加重载。
【解决方案2】:

编译器设计者是聪明的人。使用晶莹剔透,因此可维护

void setString(const std::string& str) {
    str_ = str;
}

让编译器担心优化。很好,上面加糖。

更好的是,不要将代码伪装成封装。如果您打算提供这样的方法,那么为什么不简单地制作str_public? (除非您打算在成员更改时对您的对象进行其他调整。)

最后,你为什么不喜欢std::string的默认构造函数呢?抛弃str_("")

【讨论】:

  • OP 以何种方式将代码伪装成被封装的
  • 在提供一个函数来设置成员,除了将其分配给传入的值之外,不做任何事情。
  • 传入右值的时候不做双重复制吗?
  • 理论上是的,但是好的编译器会优化它,尤其是std::string
  • 提供一个函数来设置成员,除了将其分配给传入的值之外,不做任何其他事情。 ...这就是代码正在做的事情现在 但它可能会在 未来 进行更改以执行其他操作。如果您将str_ 公开,您将无法这样做。
【解决方案3】:

带有rvalue 引用的版本通常不会绑定到lvalue(在您的情况下为mytext),您必须移动它,因此构造对象两次,给您留下一个危险的对象。从右值构造时,const lvalue reference 应该更慢,因为它会再次做同样的事情:construct -> move -> move construct

不过,编译器可能会优化开销。

您最好的选择实际上是:

void setString(std::string str) 
{
   str_ = std::move(str);
}

这里的编译器出人意料地保证推断出参数的类型,并为左值调用复制构造函数,为右值调用移动构造函数。

更新:

Chris Dew 指出,构造和移动分配字符串实际上比复制构造更昂贵。我现在确信使用const& 参数是更好的选择。 :D

【讨论】:

  • 是的,这确实是当今时尚人士打造“二传手”的方式。但是,如果您知道这一点,那么编译器也会这样做。我使用const ref 方式,因为我是老式的。一样投赞成票。
  • 我也很喜欢const ref 方式,但它的一个缺陷是它可以创建简单类型的引用,例如ints,即使复制它们会更快。我不知道这优化得有多好。
  • 这样做的问题是,在您传递左值的情况下,它不会重用str_ 的任何现有容量。如果您每次使用左值多次调用它,它将分配一个新字符串。如果您有 const std::string& 过载,那么它可以重用现有容量并避免分配。
  • @ChrheisDrew 是正确的,但是在您的设置器中使用 const ref 时,您实际上不会使用参数的容量,无论如何您都会复制(!)它。在移动构造的情况下,编译器会知道,留下的只是垃圾,所以理论上它应该优化整个setter(如果没有优化,调用临时的析构函数可以但是性能很糟糕)。
  • @ChrisDrew 好的,你的 cppcon 视频链接说服了我:D 从现在开始,我将使用 const refs 作为设置器。
【解决方案4】:

您可能会使用模板化的setString 和转发引用:

class StringWrapper
{
private:
    std::string str_;

public:
    template<typename T>
    void setString(T&& str) {
        str_ = std::forward<T>(str);
    }
};

【讨论】:

  • Herb Sutter claims 要正确执行此操作,您还必须添加一个复杂的 enable_if 表达式以将其约束为字符串和一个复杂的条件 noexcept 表达式。
猜你喜欢
  • 2022-09-28
  • 1970-01-01
  • 2017-04-03
  • 2013-12-12
  • 2010-10-02
  • 2023-03-05
  • 2010-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多