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

Performance comparison: f(std::string&&) vs f(T&&)

我正在尝试了解使用 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&& 的版本需要。我真的不明白为什么。

In this particular example, what is the performance implications of using one implementation vs the other?

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

[...] so no temporaries are created. Is this reasoning correct?

右值引用不会阻止创建临时对象。右值引用是一种能够绑定到临时对象的引用(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 右值引用然后可以绑定到。

I don't really see why.

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

带有参数 "Adela Novak"

setName(T&& newName) 将 T 推断为 const char (&)[12],然后将其分配给 std::string

setName(std::string&& newName) 使用参数 "Adela Novak" 创建一个临时 std::string 对象,然后将其移动分配给 std::string.

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

斯科特自己说 WidgetURef "compiles, but is bad, bad, bad!"(逐字)。当您使用 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的值被改变了。事实上,它至少在 ideone 上确实如此。

另一方面,WidgetRRef 要求传递的参数是一个右值引用,所以上面的例子如果没有明确的 setName(std::move(name)).

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

,这些属性将保留

假设问题中所述的参数

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

将使用 const char * 参数

为数据成员 name 调用 std::string 赋值运算符
void setName(std::string&& newName)
{
    name = std::move(newName);
}

调用 std::string 构造函数来创建一个临时的,Rvalue Ref 可以绑定到它。

使用std::string&&参数

调用std::string数据成员name的移动赋值/构造函数

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