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

歧义消解与右值/左值转换:pi为何调用拷贝构造而非移动构造?

为什么P pi((P(pa)));没有调用移动构造函数?

嘿,这个问题戳中了C++里两个经典的知识点:拷贝省略优化和最令人头疼的解析,咱们一步步把它讲清楚。

首先先重现你的测试代码和输出:

测试代码

#include <iostream>
using std::cout;
struct P {
    P() {cout << "I'm P::P()\n";};
    P(const P& p) {cout << "I'm P::P(const P&)\n";}
    P(P&& p) {cout << "I'm P::P(P&&)\n";}
};
int main() {
    P pa, pe(pa);
    P pi((P(pa))), // 确实是对象声明(括号避免了函数解析)
    pu(std::move(pa)); // 对象声明
    P po(P(pa)); // 这里才是函数声明
}

输出结果

I'm P::P()
I'm P::P(const P&)
I'm P::P(const P&)
I'm P::P(P&&)

接下来解答你的核心疑问:
你困惑为什么pi的初始化没有调用移动构造,反而看起来是拷贝构造?其实这里有个关键的优化在起作用——拷贝省略(Copy Elision),而且你的第三行输出其实不是pi的构造,而是临时对象P(pa)的构造。

咱们拆解P pi((P(pa)));的执行过程:

  1. 首先,P(pa)会创建一个临时对象:因为pa是左值,所以调用拷贝构造函数(这就是输出的第三行)。
  2. 接下来,用这个临时对象初始化pi的时候,C++标准允许编译器直接把这个临时对象构造在pi的内存空间里,完全跳过移动(或拷贝)构造函数的调用——这就是拷贝省略的一种(临时对象到同类型对象的初始化省略)。

所以你看不到pi的移动构造输出,不是因为它调用了拷贝构造,而是编译器直接优化掉了这一步!

那为什么pu(std::move(pa))会调用移动构造?因为std::move(pa)是把左值pa转换成右值引用,但这里没有临时对象,所以没有拷贝省略的机会,必须调用移动构造函数来初始化pu(这就是第四行输出)。

再补充一下你提到的P po(P(pa));为什么是函数声明:这就是C++著名的最令人头疼的解析——编译器会优先把这种语法解析成函数声明:po是一个函数,返回类型是P,参数是一个名为pa、类型为P的参数(P(pa)被解析成P pa的参数声明)。而你给pi加了一层括号(P(pa)),就打破了这种解析,让编译器把它识别为对象初始化。


最后再总结一下:

  • P(pa)是右值临时对象,它的构造调用拷贝构造(因为pa是左值)。
  • pi的初始化被编译器优化,直接复用了临时对象的构造过程,跳过了移动构造。
  • pu的初始化没有优化空间,所以调用移动构造。
  • po被解析成函数声明,根本没有创建对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:29:42