[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
方法能够高效地处理各种类型的参数传递,既能避免不必要的复制,又能正确处理字符串字面量和其他情况。
分类:
C++
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理