最小可用类需满足能实例化、调用且不崩溃,关键在于显式定义可访问构造函数(如MyClass() = default;)、合理设置访问控制(成员默认private)、资源管理时正确实现析构及拷贝/移动操作。

类和对象在 C++ 里不是“写出来就行”,而是得按内存布局、构造逻辑、访问控制三者对齐才能不出错。直接上手写 class 容易忽略默认行为,比如没写构造函数就用 MyClass obj;,可能触发未初始化的成员变量;又或者把 private 成员当成普通变量去取地址,结果被编译器拦住。
怎么定义一个最小可用的类
最小可用 ≠ 最简写法,而是满足“能实例化、能调用、不崩溃”。关键点是显式处理构造和析构,尤其当类里有指针或资源时。
- 必须至少有一个可访问的构造函数:哪怕只是空实现,也建议写
MyClass() = default;或MyClass() {},避免编译器合成的版本带来不确定性 - 成员变量默认是
private,别指望靠作用域外直接访问obj.x—— 没public就报错error: 'x' is a private member of 'MyClass' - 如果类内含原始指针(如
int* data;),不写析构函数会导致内存泄漏;不写拷贝构造/赋值,可能引发浅拷贝崩溃
示例:
class Vec2 {
public:
float x = 0.0f, y = 0.0f; // C++11 起支持非静态成员初始化
Vec2() = default; // 显式声明默认构造,避免隐式删除
Vec2(float x_, float y_) : x(x_), y(y_) {} // 带参构造,用初始化列表
};对象创建时 new / stack / static 的区别在哪
不是语法差异,是生命周期、内存位置和销毁时机的硬约束。选错一种,轻则对象提前销毁,重则堆碎片或重复析构。
立即学习“C++免费学习笔记(深入)”;
-
Vec2 a;:栈对象,作用域结束自动调用析构函数;不能返回局部栈对象的引用 -
Vec2* p = new Vec2(1.0f, 2.0f);:堆对象,需手动delete p;;忘了删就是内存泄漏;delete后再用p是悬垂指针 -
static Vec2 s;:静态存储期,整个程序生命周期存在,首次进入作用域时构造,程序退出前析构;多线程下首次初始化不是线程安全的(C++11 起函数内static变量初始化是线程安全的)
注意:new Vec2[10] 分配的是原始内存块,每个元素都调用默认构造;但 delete[] p 必须配对,用 delete p 会只析构第一个对象。
为什么 public / private / protected 不能只看“能不能访问”
它们影响的不只是编译是否通过,还决定继承关系、模板推导、ADL(参数依赖查找)甚至 ABI 兼容性。
-
private成员无法被派生类访问,也无法参与 ADL —— 即使你写了友元函数,也得显式声明在类内,否则连友元都不认 -
protected对派生类开放,但对外部代码仍不可见;若基类把某个函数设为protected,子类重写后想公开调用,必须在子类里用using Base::func;或重新声明public - 改变成员的访问级别属于 ABI 不兼容变更:哪怕只是把
private int x;改成public int x;,已编译的库链接时可能因偏移量变化而崩溃
常见误判:以为 friend 能绕过一切限制 —— 实际上它只对声明它的那个类生效,且不能继承;友元函数也不是类成员,没有 this 指针。
拷贝、移动、赋值这些操作符到底要不要自己写
取决于类是否管理资源。C++11 后有“零规则(Rule of Zero)”:如果能用 std::vector、std::string 等 RAII 类型替代裸指针,就别写任何特殊成员函数。
- 只要类里有
new/fopen/pthread_mutex_init这类资源获取操作,就必须考虑“五法则”(构造、析构、拷贝构造、拷贝赋值、移动构造/赋值) - 移动操作不是性能优化技巧,而是语义必需:例如
std::vector的push_back内部扩容时,必须用移动而非拷贝来转移旧元素,否则异常安全无法保证 - 显式禁用拷贝:用
MyClass(const MyClass&) = delete;,比不写更明确;否则编译器可能合成一个你不想要的拷贝构造函数
真正容易被忽略的是:即使你写了移动构造函数,如果成员变量本身不支持移动(比如老式类没定义移动操作),那你的移动函数实际还是调用拷贝 —— 编译器不会报错,但性能没提升。










