You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 15:37:02