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

指定构造函数时列表初始化的行为差异及相关疑问

class A {
public:
    int x;

    A() {
        std::cout << "Constructor Called !" << std::endl;
    }
    A(int y):x(y) {
        std::cout << "Constructor Param Called !" << std::endl;
    }

    A(const A& copy) {
        std::cout << "Copy Constructor Called !" << std::endl;
    }
};

class B {
public:
    A value;
    //B(const A& val) : value(val){}
};

int main(){
    B b { A(22)};
}

问题

  1. 为何注释与不注释B的构造函数时输出不同?(我了解过aggregate class和aggregate initialization,是否与此相关?)
  2. aggregate initialization与direct initialization有何区别?

解答

问题1:输出差异的原因

确实和**聚合类(aggregate class)以及聚合初始化(aggregate initialization)**直接相关:

  • 注释掉B的构造函数时,B符合聚合类的判定条件(无用户声明的构造函数、无私有/保护非静态成员、无虚函数、无基类等)。此时B b { A(22)};属于聚合初始化——编译器会直接在B的成员value的内存位置,用A(22)的参数构造对象,相当于直接调用A的带参构造,而且编译器会优化掉原本可能出现的拷贝/移动操作(哪怕拷贝构造有打印副作用也不会执行),所以只输出Constructor Param Called !。
  • 取消注释B的构造函数后,B不再是聚合类,此时B b { A(22)};会触发你定义的B的构造函数:先创建临时对象A(22)(输出带参构造日志),再把这个临时对象传给B的构造函数参数,随后在B的初始化列表中value(val)是对value做拷贝初始化,因此会调用A的拷贝构造函数,最终输出两行日志。

问题2:聚合初始化与直接初始化的区别

  • 适用场景不同:
    • 聚合初始化仅针对聚合类型生效,包括数组、满足条件的类(无用户声明构造函数、无私有/保护非静态成员、无虚函数、无基类等)。
    • 直接初始化是通用的对象初始化方式,所有能通过构造函数创建的类型都能使用。
  • 初始化逻辑不同:
    • 聚合初始化是逐个初始化对象的成员,不需要调用类的构造函数(针对聚合类而言),直接用初始化列表中的值对应初始化每个成员。比如对聚合类B,B b {A(22)}就是直接构造b.value,而非先构造临时对象再拷贝。
    • 直接初始化是通过调用目标类型的构造函数来创建对象,常见形式有T obj(args);、T obj{args};(当不属于聚合初始化场景时),比如A a(22)就是直接调用A的带参构造函数初始化a。
  • 优化特性不同:
    • 聚合初始化中,编译器可以直接跳过临时对象的拷贝/移动步骤,直接在成员的内存位置构造目标对象(如问题1中注释构造函数的情况)。
    • 直接初始化若涉及临时对象传递,是否能优化取决于具体场景和编译器版本,像问题1中取消注释构造函数的情况,因为构造函数参数传递会触发拷贝,即使C++17有保证拷贝消除,但由于你的拷贝构造有显式副作用,编译器会执行该拷贝构造逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 16:50:25