Effective Modern C++中setName函数临时std::string对象的疑问
背景与代码示例
近期学习《Effective Modern C++11/14》第171页第25项时,作者给出了setName函数的两种实现示例:
实现1:万能引用版本
// Option 1, universal reference class Widget { public: template<typename T> void setName(T &&newName) // universal reference { name = std::move(newName); }// compiles, but is bad, bad, bad! //… private: std::string name; std::shared_ptr<SomeDataStructure> p; }; void main() { Widget w; w.setName("Adela Novak"); }
实现2:左值+右值引用重载版本
// Option 2, lvalue reference and rvalue reference class Widget2 { public: void setName(const std::string &newName) { name = newName; } void setName(std::string &&newName) { name = std::move(newName); } //… }; void main() { Widget2 w; w.setName("Adela Novak"); }
作者观点
使用万能引用版setName时,字符串字面量直接赋值给w的std::string成员,不会产生临时std::string对象;而重载版会先创建临时std::string对象绑定到参数,再将其移动到成员中。
个人疑问
我的理解是:两种实现都会在main中隐式为"Adela Novak"创建临时std::string,但选项1仅创建1个,选项2需创建2个,因此有以下三个技术疑问:
- 为什么选项1不需要为
newName创建额外的临时std::string对象? - 为什么选项2需要为
newName创建额外的临时std::string对象? - 如何判断调用的成员函数是否会创建临时对象?
ChatGPT给出的回答
在选项1中,字符串字面量"Adela Novak"传入setName时,T被推导为const char*,newName为右值引用,std::move触发std::string的移动构造,仅创建1个临时std::string。
在选项2中,编译器先创建临时std::string匹配参数,再触发拷贝构造创建第二个临时对象,最终移动到成员中,共创建2个临时std::string。
疑问解答
1. 选项1无需额外临时对象的原因
万能引用的模板参数推导特性是核心:当传入字符串字面量"Adela Novak"(类型退化为const char*)时,模板参数T会被推导为const char*,此时newName的类型是const char* &&(右值引用)。
执行name = std::move(newName)时,std::move(newName)返回const char* &&,而std::string的赋值运算符支持直接接收const char*类型的右值,这个过程会直接用字面量初始化name的内部字符缓冲区,不需要额外创建临时std::string对象——本质是调用std::string& operator=(const char*),而非移动或拷贝构造。
2. 选项2需要额外临时对象的原因
选项2的重载函数参数均为std::string相关引用,但字符串字面量并非std::string类型,编译器必须先通过std::string(const char*)构造函数创建一个临时std::string对象,才能匹配到const std::string&或std::string&&参数。
无论最终匹配哪个重载,都必须先创建临时std::string来绑定函数参数,这就是作者所说的额外临时对象。后续的赋值操作(拷贝或移动)都是基于这个临时对象完成的。
3. 判断成员函数是否创建临时对象的方法
- 检查类型匹配度:如果实参类型与函数参数类型(含推导后的引用类型)完全匹配,通常不会创建临时对象;若需要类型转换(如
const char*转std::string),则必然会生成临时对象完成转换。 - 追踪操作调用链:查看函数内部对参数的操作,若直接调用目标类型(如
std::string)的构造/赋值运算符接收原始实参类型,则无临时对象;若参数是目标类型的引用,但实参不是目标类型,就需要先构造临时对象绑定参数。 - 区分代码特性与编译器优化:RVO(返回值优化)等编译器优化可能消除临时对象,但这是编译器的行为,而非代码本身的特性,不能作为判断的依据,需以代码逻辑的固有行为为准。
内容的提问来源于stack exchange,提问作者Peter

