为何C++中foo和bar返回tuple时移动/拷贝行为存在差异?
为何
bar()触发拷贝构造而foo()触发移动构造? 示例代码
#include <iostream> #include <tuple> struct MyStruct { MyStruct() = default; MyStruct(const MyStruct&) { std::cout << "Copy constructor called" << std::endl; } MyStruct(MyStruct&&) noexcept { std::cout << "Move constructor called" << std::endl; } ~MyStruct() = default; MyStruct& operator=(const MyStruct&) = default; MyStruct& operator=(MyStruct&&) noexcept = default; }; std::tuple<MyStruct, MyStruct> foo() { return {MyStruct{}, MyStruct{}}; } std::tuple<MyStruct, MyStruct> bar() { return {{}, {}}; } int main() { std::cout << "Foo" << std::endl; auto [a, b] = foo(); std::cout << "Bar" << std::endl; auto [c, d] = bar(); return 0; }
运行输出
Foo Move constructor called Move constructor called Bar Copy constructor called Copy constructor called
问题解析
两者的行为差异核心在于tuple的初始化方式不同,导致结构化绑定时成员的取值类别(value category)不同:
1. foo()的移动构造逻辑
foo()中return {MyStruct{}, MyStruct{}}显式创建两个临时MyStruct对象(右值/将亡值),并调用std::tuple的构造函数接收这两个右值。tuple的构造过程会利用MyStruct的移动构造函数,将临时对象移动到自身成员中。
当执行结构化绑定auto [a,b] = foo();时,foo()返回的tuple是纯右值(prvalue),C++17的保证拷贝消除会直接在绑定的临时内存位置构造该tuple。此时std::get提取tuple成员时返回的是右值引用,因此a和b会通过移动构造完成初始化,触发两次移动构造调用。
2. bar()的拷贝构造逻辑
bar()中return {{}, {}}是对std::tuple进行聚合初始化——tuple的两个MyStruct成员会直接在tuple的内存空间中默认构造(通过{}值初始化),没有经过移动操作。
在结构化绑定时,编译器将聚合初始化得到的tuple成员视为左值(因为成员是直接构造在tuple中的,编译器默认认为它们需要被保留),因此std::get提取成员时返回左值引用,初始化c和d只能调用拷贝构造函数,触发两次拷贝构造。
注:cppinsights的显示存在偏差,它错误地将聚合初始化转换为构造函数调用,导致看起来两个函数逻辑一致,但实际初始化机制完全不同。
内容的提问来源于stack exchange,提问作者Joan Marcè i Igual
相关产品推荐
相关产品推荐

