const成员变量必须在构造函数初始化列表中初始化,不能在函数体内赋值;仅加const不足以保证逻辑不可变,还需确保所持资源本身不可变、禁用赋值操作符,并使整个对象图各层均不可变。

为什么直接用 const 成员变量还不够
声明 const 成员变量只是第一步,但 C++ 不允许在构造函数体内给 const 成员赋值——它必须在初始化列表中完成。如果漏写、写错顺序,或试图在构造函数体里赋值,编译器会报错:error: uninitialized const member 或 error: assignment of read-only member。更隐蔽的问题是:若成员本身是类类型且没有 const 构造函数,或内部持有裸指针/引用,即使加了 const,对象仍可能被间接修改。
初始化列表是强制入口,不是可选项
所有 const 成员、引用成员、没有默认构造函数的类类型成员,都必须出现在构造函数的初始化列表中。否则编译失败。常见错误包括:
- 把初始化写在构造函数体里:
this->x = 42;→ 编译失败 - 初始化顺序和声明顺序不一致 → 值未定义(虽不报错,但可能读到垃圾值)
- 用
this->在初始化列表中调用成员函数 → 不合法,此时对象尚未构造完成
正确写法示例:
class Point {
const int x_;
const int y_;
public:
Point(int x, int y) : x_(x), y_(y) {} // ✅ 必须这样
};
如何防止通过指针/引用绕过不可变性
仅靠 const 成员无法阻止“逻辑可变”:比如成员是 const std::vector<int>*,指针本身不可变,但指向的内容仍可改;又或者成员是 const std::shared_ptr<Data>,智能指针不可变,但 Data 对象本身非 const。要真正 Immutable,需满足:
立即学习“C++免费学习笔记(深入)”;
- 所有数据成员类型本身是值语义且不可变(如
const int、const std::string) - 避免裸指针、非
const引用、可变智能指针持有可变对象 - 若必须持有外部资源,确保其接口只暴露
const方法,并在类内封装访问路径
反例:const std::unique_ptr<int> ptr_; → 指针不可变,但 *ptr_ = 100; 合法;正解是用 const std::unique_ptr<const int> 或干脆用值类型。
拷贝与移动构造函数也要显式约束
默认生成的拷贝/移动构造函数不会自动保留 const 语义——它们只是按位复制,但若类中有指针或资源句柄,就可能破坏不可变性假设。更关键的是:如果你希望该类完全禁止修改,应禁用赋值操作符:
- 显式删除:
Point& operator=(const Point&) = delete; - 若类含非 trivial 类型(如
std::mutex),默认移动构造可能不生成,需手动实现并确保只做 const 成员的转移 - 移动构造函数参数也应为
const Point&&?不,标准做法是Point(Point&&),但内部只能访问const成员,所以实际仍是安全的
典型防御写法:
class ImmutableConfig {
const std::string host_;
const uint16_t port_;
public:
ImmutableConfig(std::string h, uint16_t p) : host_(std::move(h)), port_(p) {}
ImmutableConfig(const ImmutableConfig&) = default;
ImmutableConfig(ImmutableConfig&&) = default;
ImmutableConfig& operator=(const ImmutableConfig&) = delete;
ImmutableConfig& operator=(ImmutableConfig&&) = delete;
};
真正难的不是加 const,而是让整个对象图(object graph)里每层都不可变——哪怕一个 const 成员持有一个非 const 的 std::shared_ptr,就等于留了后门。


















