[Effective Modern C++] 条款24笔记 - 一个关于std::move与std::forward<T>的小例子
std::move
首先,有如下代码:
class Widget {
public:
void setName(const std::string& newName) // set from
{ name = newName; } // const lvalue
void setName(std::string&& newName) // set from
{ name = std::move(newName); } // rvalue
…
};
Widget w;
w.setName("Adela Novak);
如果有上述Widget类,调用w.setName("Adela Novak");语句。调用了哪些函数,实际进行了哪些操作?
在类 Widget 中,存在两个重载的 setName 成员函数,一个接受 const std::string& 参数,另一个接受 std::string&& 参数。它们分别用于处理左值和右值引用。
调用 w.setName("Adela Novak"); 时,会调用如下重载的函数:
void setName(std::string&& newName) // set from
{ name = std::move(newName); } // rvalue
以下是具体的操作过程:
-
字符串字面量转换:
"Adela Novak"是一个字符串字面量,它的类型是const char[12]。- 在调用
w.setName("Adela Novak");时,这个字符串字面量首先被转换为一个临时的std::string对象。
-
右值引用绑定:
- 由于临时的
std::string是一个右值,编译器会选择调用setName(std::string&& newName)函数。 newName参数被绑定到这个临时的std::string对象。
- 由于临时的
-
move 语义:
- 在
setName(std::string&& newName)函数体内,调用了std::move(newName)。 std::move(newName)将newName转换为一个右值引用。name = std::move(newName)将newName中的资源(如内存缓冲区)移动到name成员变量中,而不是复制。
- 在
通过 std::move,name 成员变量接管了临时 std::string 的资源,从而避免了不必要的复制操作,提高了效率。
调用过程的总结
"Adela Novak"被转换为一个临时的std::string。- 临时
std::string被传递给setName(std::string&& newName)。 newName被移动赋值给name成员变量。
这种方式利用了 C++11 引入的右值引用和 move 语义,减少了不必要的资源开销,提高了性能。
std::forward
如果将Widget的实现,更改为模板形式,如下:
class Widget {
public:
template<typename T>
void setName(T&& newName) {
name = std::forward<T>(newName); // Perfect forwarding
}
private:
std::string name;
std::shared_ptr<SomeDataStructure> p;
};
改进后的代码使用了完美转发,通过 std::forward 实现了对左值和右值的正确处理。以下是详细的调用分析:
调用分析
1. 调用 w.setName("Adela Novak");
-
模板参数推导:
"Adela Novak"是一个字符串字面量,类型为const char[12]。- 模板参数
T被推导为const char[12]。 - 因此,
T&&被推导为const char(&&)[12]。
-
函数实例化:
- 实例化后的函数签名为
void setName(const char(&&)[12])。
- 实例化后的函数签名为
-
std::forward转发:std::forward<T>(newName)等价于std::forward<const char[12]>(newName)。- 对于右值引用,
std::forward仍然会返回newName的右值引用类型。 - 然而,
std::string构造函数无法接受一个const char[12]右值引用,因此会调用std::string的构造函数,传递一个const char*。
-
std::string构造函数:name的赋值操作会调用std::string的构造函数,将const char*转换为std::string对象。
2. 调用 std::string someString = "Adela Novak"; w.setName(someString);
-
模板参数推导:
someString是一个左值,类型为std::string。- 模板参数
T被推导为std::string&。 - 因此,
T&&被推导为std::string& &&,根据引用折叠规则,简化为std::string&。
-
函数实例化:
- 实例化后的函数签名为
void setName(std::string&)。
- 实例化后的函数签名为
-
std::forward转发:std::forward<T>(newName)等价于std::forward<std::string&>(newName)。- 对于左值引用,
std::forward返回newName的左值引用类型。
-
std::string赋值操作:name的赋值操作会调用std::string的赋值运算符,将someString复制给name。
3. 调用 w.setName(std::move(someString));
-
模板参数推导:
std::move(someString)是一个右值,类型为std::string&&。- 模板参数
T被推导为std::string。 - 因此,
T&&被推导为std::string&&。
-
函数实例化:
- 实例化后的函数签名为
void setName(std::string&&)。
- 实例化后的函数签名为
-
std::forward转发:std::forward<T>(newName)等价于std::forward<std::string>(newName)。- 对于右值引用,
std::forward返回newName的右值引用类型。
-
std::string赋值操作:name的赋值操作会调用std::string的移动赋值运算符,将someString的资源移动给name,避免了不必要的复制。
总结
- 左值传递: 当传递左值时,
std::forward保持左值引用,调用复制操作。 - 右值传递: 当传递右值时,
std::forward转发右值引用,调用移动操作。 - 字符串字面量: 字符串字面量被正确地转换为
std::string,并使用相应的构造函数进行赋值。
通过使用完美转发,确保了 setName 方法能够高效地处理各种类型的参数传递,既能避免不必要的复制,又能正确处理字符串字面量和其他情况。

浙公网安备 33010602011771号