C++使用建造者模式时如何避免多次调用构造函数
问题根因
你遇到的拷贝构造触发,是因为add_field返回左值引用,即使调用方是临时右值,返回值仍是左值,拷贝初始化时无法触发移动语义,只能执行拷贝。你尝试的左值引用绑定临时对象的写法属于未定义行为,临时对象在表达式结束后即析构,引用悬空会导致乱码,不可用。
以下是可落地的优化方案,均可实现仅一次有效构造、无多余拷贝的效果:
方案1:拆分构造与链式调用(零额外开销,最易实现)
直接先在栈上构造目标对象,再调用链式方法,完全避免临时对象产生,全程只会调用一次构造函数:
CodeBuilder code{"Dog"}; code.add_field("name", "std::string").add_field("legs", "int");
该写法无任何临时对象生成,自然没有拷贝、移动开销,运行逻辑完全合法。
方案2:添加移动构造+显式std::move调用(保留链式初始化写法)
如果你需要保留一行完成链式初始化的写法,首先给CodeBuilder和MyString补充移动构造函数,再在初始化时显式将返回的左值转为右值,触发移动语义:
补充移动构造代码:
class MyString { std::string str; public: // 原有实现保持不变,新增移动构造 MyString(MyString&& other) noexcept : str(std::move(other.str)) { // 仅调试用,业务代码可删除打印 std::cout << "MyString Move Ctor" << std::endl; } }; class CodeBuilder { MyString _code; public: CodeBuilder(const std::string& class_name) { /*原有实现*/ } // 新增移动构造 CodeBuilder(CodeBuilder&& other) noexcept : _code(std::move(other._code)) { // 仅调试用,业务代码可删除打印 std::cout << "CodeBuilder Move Ctor" << std::endl; } CodeBuilder& add_field(const std::string& name, const std::string& type) { /*原有实现*/ return *this; } };
调整初始化写法:
CodeBuilder code = std::move(CodeBuilder{"Dog"}.add_field("name", "std::string").add_field("legs", "int"));
该方案仅会产生一次默认构造+一次移动构造,移动操作的开销极低。如果你使用C++17及以上版本,编译器还会触发强制复制消除,连移动构造都会被省略,直接将临时对象构造到code的内存空间。
方案3:重载右值限定的add_field(原有调用写法无需修改)
如果你不想调整业务侧的调用代码,可以给add_field增加引用限定符重载,右值对象调用时自动返回右值引用,自动触发移动/复制消除:
class CodeBuilder { MyString _code; public: CodeBuilder(const std::string& class_name) { /*原有实现*/ } CodeBuilder(CodeBuilder&& other) noexcept : _code(std::move(other._code)) {} // 左值对象调用时返回左值引用 CodeBuilder& add_field(const std::string& name, const std::string& type) & { /*原有实现*/ return *this; } // 右值对象调用时返回右值引用 CodeBuilder&& add_field(const std::string& name, const std::string& type) && { /*原有实现*/ return std::move(*this); } };
修改后你原有调用代码不需要做任何调整,编译器会自动识别链式调用的是右值对象,最终返回右值引用触发移动构造或C++17强制复制消除,全程无多余拷贝开销。
内容的提问来源于stack exchange,提问作者DeSpeaker
相关产品推荐
相关产品推荐

