我试图了解使用WidgetURef::setName
(URef
是一个通用参考,由Scott Meyers创造的术语)与WidgedRRef::setName
(RRef
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&&
。我真的不明白为什么。
答案 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
作为参数传递,则WidgetRRef
和std::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&&
析构函数来销毁临时数据,我们从中移动数据。