3

我刚开始使用 c++11 r-values。我阅读了一些教程,但我还没有找到答案。

设置类变量的最佳方式(最有效的方式)是什么?下面的代码是否正确?(假设 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
}

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

4

4 回答 4

4

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

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

你最好的选择实际上是:

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

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

更新:

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

于 2017-06-22T14:16:32.340 回答
4

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

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

并让编译器担心优化。拜托了,上面加糖。

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

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

于 2017-06-22T14:03:46.320 回答
4

Herb Sutter对此的建议是从标准 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&过载,那么它可以重新使用现有容量并避免分配。

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

于 2017-06-22T14:59:47.413 回答
3

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

class StringWrapper
{
private:
    std::string str_;

public:
    template<typename T>
    void setString(T&& str) {
        str_ = std::forward<T>(str);
    }
};
于 2017-06-22T14:24:33.970 回答