编译器仅在类中未声明任何构造函数时隐式声明默认构造函数;若被odr使用,则检查可行性并定义(或标记为deleted),如含const/引用成员且无默认初始化器即删除。

编译器不会因为你没写构造函数就“一定”生成默认构造函数。它只在满足特定条件、且该函数被实际需要时,才隐式声明并可能定义一个默认构造函数;而一旦存在任何阻止其合法调用的语义障碍,这个函数就会被标记为 deleted,导致编译失败。
什么时候编译器会隐式声明默认构造函数
这是第一步,也是最宽松的条件:只要类中**没有任何用户声明的构造函数**(注意是“声明”,不是“定义”),编译器就会在类定义结束时悄悄加上一行 ClassName() = default; 的声明 —— 无论成员有没有 const、引用、或不可默认构造的类型。
这个动作不检查可行性,纯属语法补全。所以以下代码能通过编译:
struct Bad {
const int x;
Bad() = default; // OK:声明没问题
};
但真正调用时才会出错:
Bad b; // error: use of deleted function 'Bad::Bad()'
- 隐式声明发生在类定义阶段,与后续是否能用无关
- 哪怕你只声明了一个带参构造函数(如
Widget(int)),这一步就直接跳过 - 有
= delete的构造函数也算“用户声明”,同样阻止隐式声明
什么时候编译器会真正定义(即生成函数体)
定义只发生在默认构造函数**被 ODR-used**(odr-used,即不仅声明,还被取地址、调用、或用于对象初始化)的那一刻。此时编译器才去检查:这个函数能不能合法执行?
如果不能,就把它标记为 deleted,而不是生成空函数体。
- 成员是
const类型且无默认成员初始化器 →deleted - 成员是引用类型且无默认成员初始化器 →
deleted - 某个非静态成员或基类本身没有可用的默认构造函数 →
deleted - C++11 起,若提供了默认成员初始化器(如
const int x = 42;),上述前两条不再触发删除
例如:
struct A {
const int x = 0; // 有默认成员初始化器
A() = default; // OK:定义成功,x 被初始化为 0
};
哪些场景会强制触发隐式定义(即使你没显式调用)
有些看似“没用到”的地方,其实已经构成 ODR-use,会逼编译器当场生成定义并检查合法性:
- 栈上定义对象:
A a; - 堆上 new 对象:
new A; - 作为函数参数按值传入:
void f(A); f(A{}); - 作为返回类型:
A make_a() { return {}; } - 聚合初始化中省略初始化器:
A arr[2] = {}; // 默认初始化第一个元素
这些都不是“可选行为”,而是语言规则强制要求:只要语法允许默认构造发生,编译器就必须尝试定义它。
虚函数和虚继承也会触发隐式声明
即使类完全空着,只要含虚函数或继承虚基类,编译器就必须提供默认构造函数来设置虚表指针(vptr)或虚基类偏移信息。
struct V {
virtual void f() {}
}; // 编译器必须隐式声明 V(),否则无法设置 vptr
这类构造函数通常不会被删除(除非你手动加了冲突成员),因为虚机制本身不引入初始化约束。
真正容易忽略的是:隐式声明和隐式定义是两步,中间隔着“是否被用到”这个关键节点;很多报错出现在你新增一行 X x; 后,而不是改类定义时 —— 因为那才是定义真正落地的时刻。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











