C++用临时对象初始化类成员时如何保证仅调用一次其构造函数
问题原因与解决方案
为什么你的代码触发了拷贝构造?
你定义的Outer构造函数中,形参inner虽然是T&&右值引用类型,但有名称的右值引用本身属于左值,你在初始化列表中直接用inner(inner)时,传入的是左值,自然匹配Inner的拷贝构造函数,不会触发移动逻辑。
如果要先改成触发移动构造,只需要修改Outer构造函数的初始化列表,对参数套std::move:
template<typename T> struct Outer { T inner; Outer(T&& inner) : inner(std::move(inner)) {} // 加std::move };
修改后运行输出会变成:
ctor move ctor dtor dtor
已经消除了拷贝,但还是存在两次构造(一次临时对象构造,一次移动构造)。
实现仅一次Inner构造的方案
如果要完全避免额外的构造/移动操作,直接在Outer的inner成员的内存地址上构造对象,可以给Outer添加完美转发构造函数,直接把构造Inner所需的参数透传给inner的构造函数,不需要产生中间临时对象:
#include <stdio.h> #include <utility> #include <type_traits> template<typename T> struct Outer { T inner; // 完美转发构造:接收所有可用于构造T的参数,直接原地构造inner template<typename... Args> requires std::is_constructible_v<T, Args...> // 约束避免和拷贝/移动构造冲突 Outer(Args&&... args) : inner(std::forward<Args>(args)...) {} }; struct Inner { int x; int y; Inner(const int x, const int y) : x(x), y(y) { printf("ctor\n"); } Inner(const Inner& rhs) : x(rhs.x), y(rhs.y) { printf("copy ctor\n"); } Inner(Inner&& rhs) : x(std::exchange(rhs.x, 0)), y(std::exchange(rhs.y, 0)) { printf("move ctor\n"); } Inner& operator=(const Inner& rhs) { printf("assign\n"); return *this = Inner(rhs); } Inner& operator=(Inner&& rhs) { printf("move assign\n"); std::swap(x, rhs.x); std::swap(y, rhs.y); return *this; } ~Inner() { printf("dtor\n"); } }; int main() { Outer<Inner> wrapper(123, 234); // 直接传构造参数,不需要生成临时Inner return 0; }
运行输出如下,仅触发一次构造、一次析构,符合你的要求:
ctor dtor
如果你一定要保留Outer<Inner> wrapper(Inner(123, 234));的传临时对象写法,在C++17及以上版本配合开启编译优化,部分编译器也可以通过强制复制消除直接把临时对象构造到Outer的inner成员位置,跳过移动构造,同样实现仅一次构造。
内容的提问来源于stack exchange,提问作者moog
相关产品推荐
相关产品推荐

