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

C++移动语义下临时对象与函数返回行为的技术咨询

C++移动语义场景下的行为疑问解答

近期复习C++并重新学习移动语义,编写了如下示例程序并得到对应输出,但对其中步骤3、4、5的行为存在疑惑。

示例程序

#include <iostream>
#include <iomanip>

int sepI = 0;
void printSep(const char* msg) {
    std::cout
        << std::setw(40) << std::setfill('>') << "" << std::setfill(' ')
        << std::setw(2) << std::setiosflags(std::ios::right) << sepI++ << ": " << std::resetiosflags(std::ios::right)
        << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
        << std::endl;
}

void printHook(const char* msg, void* p) {
    std::cout
        << std::setw(25) << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
        << std::setw(15) << std::setiosflags(std::ios::right) << p << std::resetiosflags(std::ios::right)
        << std::endl;
}

class MyT {
public:
    MyT() { printHook("constructor call", this); }
    MyT(MyT& t) { printHook("copy constructor call", this); }
    MyT(MyT&& t) { printHook("move constructor call", this); }

    ~MyT() { printHook("destructor call", this); v = 0; }

    MyT& operator=(MyT& t) { printHook("copy assigment call", this); return *this; }
    MyT& operator=(MyT&& t) { printHook("move assigment call", this); return *this; }

    int v = 1;
};

MyT fn() { return MyT(); }

int main() {
    printSep("a");
    MyT a;

    printSep("MyT&& v1 = std::move(a)");

    MyT&& v1 = std::move(a);
    std::cout << v1.v << " " << &v1 << std::endl;

    printSep("MyT&& v2 = MyT()");

    MyT&& v2 = MyT();
    std::cout << v2.v << " " << &v2 << std::endl;

    printSep("MyT&& v3 = std::move(MyT())");

    MyT&& v3 = std::move(MyT());
    std::cout << v3.v << " " << &v3 << std::endl;

    printSep("MyT&& v4 = fn()");

    MyT&& v4 = fn();
    std::cout << v4.v << " " << &v4 << std::endl;

    printSep("MyT&& v5 = std::move(fn())");

    MyT&& v5 = std::move(fn());
    std::cout << v5.v << " " << &v5 << std::endl;

    printSep("end");

    return 0;
}

程序输出

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 0: a                            OK.
constructor call          0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1: MyT&& v1 = std::move(a)      OK. Makes sense.
1 0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2: MyT&& v2 = MyT()             OK. Makes sense.
constructor call          0x7ffe656eeae8
1 0x7ffe656eeae8
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 3: MyT&& v3 = std::move(MyT())  NOT OK.
constructor call          0x7ffe656eeaec
destructor call           0x7ffe656eeaec
1 0x7ffe656eeaec
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 4: MyT&& v4 = fn()              NOT OK.
constructor call          0x7ffe656eeaf0
1 0x7ffe656eeaf0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5: MyT&& v5 = std::move(fn())   NOT OK.
constructor call          0x7ffe656eeaf4
destructor call           0x7ffe656eeaf4
1 0x7ffe656eeaf4
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 6: end
destructor call           0x7ffe656eeaf4
destructor call           0x7ffe656eeaf0
destructor call           0x7ffe656eeae8
destructor call           0x7ffe656eeae0

疑问与解答

步骤3相关解答

  1. 执行过程:

    • 首先创建临时对象MyT(),调用默认构造函数;
    • 调用std::move()将该临时对象转换为右值引用(注意std::move仅做类型转换,不触发任何构造/移动操作);
    • 用转换后的右值引用初始化v3,但此时编译器无法触发临时对象生命周期延长规则,临时对象在当前表达式结束后立即被销毁,调用析构函数;
    • 后续v3实际绑定的是已销毁对象的内存地址,打印v3.v属于访问已销毁的栈内存。
  2. 与步骤2的差异原因:
    步骤2的MyT&& v2 = MyT()触发了C++的临时对象生命周期延长规则:当右值引用直接绑定到临时对象时,临时对象的生命周期会被延长至与引用变量v2一致。而步骤3中,std::move(MyT())返回的是一个右值引用,v3绑定的是这个引用的结果而非原始临时对象,因此生命周期延长规则不生效,临时对象会立即销毁。

  3. 未触发段错误的原因:
    访问已销毁对象的内存属于未定义行为,未定义行为不保证一定会触发段错误。此处地址属于栈内存,对象销毁后该内存未被立即覆盖或回收,因此读取v3.v时刚好能读到旧值,但这种行为完全不可靠,更换编译选项或运行环境就可能出现崩溃。

步骤4和5相关解答

  1. fn()返回实例的内存位置:
    由于编译时开启了-O3优化,编译器触发了返回值优化(RVO),fn()中创建的MyT()临时对象会直接在main()函数的栈帧中分配内存,即直接构造在v4绑定的栈地址上,完全避免了拷贝/移动操作。

  2. 两次调用地址不同且无动态分配的原因:
    两次调用fn()时,编译器会在main()的栈帧中为每次返回的对象分配独立的栈空间(栈内存按调用顺序依次分配),因此得到不同的地址。这些对象都分配在栈上,无需new/malloc等动态分配操作。而步骤5中std::move(fn())破坏了生命周期延长规则,导致fn()返回的临时对象在表达式结束后立即销毁,v5绑定的是已销毁的对象地址。

内容的提问来源于stack exchange,提问作者Artsiom Miksiuk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 10:46:20