性能比较:f(std :: string&&)vs f(T&&)

时间:2018-05-29 10:04:32

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

我试图了解使用WidgetURef::setNameURef是一个通用参考,由Scott Meyers创造的术语)与WidgedRRef::setNameRRef R值参考):

#include <string>

class WidgetURef {
    public:
    template<typename T>
    void setName(T&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

class WidgetRRef {
    public:
    void setName(std::string&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

int main() {
    WidgetURef w_uref;
    w_uref.setName("Adela Novak");

    WidgetRRef w_rref;
    w_rref.setName("Adela Novak");
}

我很欣赏通用引用应该使用std::forward代替,但这只是一个(不完美的)示例,以突出有趣的位。

问题 在这个特定的例子中,使用一个实现与另一个实现的性能影响是什么?虽然WidgetURef需要类型扣除,但它与WidgetRRef完全相同,不是吗?至少在这种特定情况下,在两种情况下,参数都是r-value引用,因此不会创建临时值。这种推理是否正确?

上下文 这个例子来自Scott Meyers的“Effective Modern C ++”(第170页)第25项。根据这本书(假设我的理解是正确的!),采用通用引用T&&的版本不需要临时对象,另一个采用std::string&&。我真的不明白为什么。

4 个答案:

答案 0 :(得分:5)

带有参数setName(T&& newName)

"Adela Novak"被定义为const char (&)[12],然后将其分配给std::string

参数setName(std::string&& newName)

"Adela Novak"会创建一个临时std::string对象,然后将其移至std::string

第一个在这里效率更高,因为没有涉及移动。

答案 1 :(得分:4)

  

在这个特定的例子中,使用一个实现与另一个实现的性能影响是什么?

正如Scott Meyers所说的那样,普遍引用主要不是出于性能原因,而是松散地说,以相同的方式处理L-和Rvalue引用以避免无数次重载(以及能够传播所有类型)转发期间的信息。)

  

[...]所以没有创造临时工。这种推理是否正确?

Rvalue引用不会阻止创建临时值。 Rvalue引用是能够绑定到临时值的引用类型(除了const值左侧引用)!当然,在你的例子中,会有临时值,但是右值引用可以绑定它。通用引用首先必须经历引用崩溃,但最终,行为在您的情况下将是相同的:

// explicitly created temporary
w_uref.setName(std::string("Adela Novak")); 
// will create temporary of std::string --> uref collapses to rvalue ref
// so is effectively the same as
w_rref.setName("Adela Novak");

另一方面,通过使用右值引用,可以隐式强制临时,因为std::string&&无法绑定到该文字。

w_rref.setName("Adela Novak"); // need conversion

因此,编译器将从文本中创建一个临时std::string,然后可以绑定到rvalue引用。

  

我真的不明白为什么。

在这种情况下,模板将被解析为const char(&)[12],因此,与上述情况相比,不会创建std::string临时模板。因此,这更有效。

答案 2 :(得分:2)

斯科特自己说WidgetURef&#34;编译,但坏,坏,坏!&#34; (逐字)。当您使用std::move而不是std::forward时,这两个类的行为会有所不同:setName因此可以修改其参数:

#include <string>
#include <iostream>

class WidgetURef {
    public:
    template<typename T>
    void setName(T&& newName)
    {
        name = std::move(newName);
    }
    private:
        std::string name;
};

int main() {
    WidgetURef w_uref;
    std::string name = "Hello";
    w_uref.setName(name);
    std::cout << "name=" << name << "\n";
}

可以轻松打印name=,这意味着name的值已更改。事实上,它至少在想法上也是如此。

另一方面,WidgetRRef 要求传递的参数是rvalue-reference,所以上面的例子不会在没有显式setName(std::move(name))的情况下进行编译。 / p>

如果您将WidgetURef作为参数传递,则WidgetRRefstd::string都不需要创建额外的副本。但是,如果您传递了std::string可以分配的内容(例如const char*),那么第一个示例将通过引用传递它并将其分配给字符串(除了从C复制数据之外没有任何副本) -style string into std::string),第二个示例将首先创建一个临时string,然后将其作为方法的右值引用传递给它。如果您使用正确的std::move(newName)替换std::forward<T>(newName),则会保留这些属性。

答案 3 :(得分:0)

假设问题中陈述的论点

template<typename T>
void setName(T&& newName)
{
    name = std::forward<T>(newName);
}

将使用std::string参数调用数据成员name的{​​{1}}赋值运算符

const char *

调用void setName(std::string&& newName) { name = std::move(newName); } 构造函数来创建一个临时值,Rvalue Ref可以绑定到该临时值。

使用std::string参数为数据成员std::string调用name移动分配/构造函数

调用std::string&&析构函数来销毁临时数据,我们从中移动数据。