const成员变量必须在构造函数初始化列表中赋值,因构造函数体内赋值会违反不可变性;需配合删除赋值操作符、私有构造+工厂函数及const访问器,确保深不可变性。

const 成员变量必须在构造函数初始化列表中赋值
在 C++ 中,const 成员变量一旦声明,就不能在构造函数体内部用赋值语句修改——编译器会直接报错:error: assignment of read-only member。这不是语法限制,而是语言设计上对“不可变性”的强制保障:对象创建后,const 成员的值必须已确定,且永不变更。
正确做法只有一种:通过构造函数的**成员初始化列表**完成赋值。例如:
class Point {
const int x_;
const int y_;
public:
Point(int x, int y) : x_(x), y_(y) {} // ✅ 正确:初始化列表
// Point(int x, int y) { x_ = x; y_ = y; } // ❌ 编译失败
};
- 即使
x_和y_是内置类型(如int),也必须走初始化列表;不能依赖默认构造+后续赋值 - 若成员是自定义类型(如
const std::string name_),其构造逻辑也完全发生在初始化列表阶段,不会调用默认构造函数再赋值 - 初始化顺序严格按类中声明顺序,而非初始化列表书写顺序——写错顺序可能引发未定义行为(尤其当某成员依赖另一成员初始化时)
不可变类需要禁用拷贝赋值和移动赋值操作符
仅靠 const 成员无法阻止对象被“覆盖”。如果用户写出 p1 = p2;,编译器会尝试调用默认生成的拷贝赋值运算符——它会对每个成员执行赋值操作,而 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;
};
-
= default保留拷贝/移动构造是合理的:它们不修改已有对象,只是新建一个副本 -
= delete拷贝赋值和移动赋值,确保任何=表达式在编译期就被拦截 - 如果不删,某些编译器可能在优化下绕过
const检查(极少见但存在),显式删除是最可靠防线
const 成员 + private 构造 + 工厂函数 = 更强封装控制
仅靠 const 成员和删除赋值操作符,还不能防止外部绕过构造逻辑、通过友元或继承注入非法状态。比如有人继承你的类并访问基类 protected 成员(如果开放了),或者用 friend 函数强行修改(虽然 const 仍拦得住,但设计意图被破坏)。
更稳健的做法是把构造函数设为 private,提供静态工厂函数,并配合 friend(仅限必要场景)或内部嵌套 builder:
class ImmutableUri {
const std::string scheme_;
const std::string host_;
const uint16_t port_;
ImmutableUri(std::string s, std::string h, uint16_t p)
: scheme_(std::move(s)), host_(std::move(h)), port_(p) {}
public:
static ImmutableUri from_string(const std::string& uri) {
// 解析逻辑……校验 scheme/host/port 合法性
return ImmutableUri{"https", "api.example.com", 443};
}
// 所有访问器都应是 const 成员函数
const std::string& scheme() const { return scheme_; }
const std::string& host() const { return host_; }
uint16_t port() const { return port_; }
};
- 工厂函数可以集中做参数校验、规范化(如统一小写 scheme)、甚至缓存实例(避免重复构造)
- 所有 getter 必须声明为
const成员函数,否则无法在const对象上调用 - 避免返回
const引用以外的非常量引用或指针——那会间接暴露可变入口
注意 const 成员的深不可变性陷阱
const 只保证成员变量本身的值不可变,不保证其所指向内容不可变。比如 const std::unique_ptr 是合法的,但它指向的 int 依然可被修改:*ptr_ = 42; 完全可行。这违反了“不可变类”的直觉预期。
- 若成员是指针或智能指针,需确保其指向的对象本身也是不可变的(例如指向
const int或不可变结构体) - 容器类如
const std::vector可以防止增删改 size 和元素值,但若元素是裸指针,仍可能间接修改目标对象 - 真正安全的不可变组合:用
const成员 + 值语义类型(std::string,std::array)+ 不含裸指针/非 const 引用的嵌套结构
所谓不可变,最终取决于整个对象图谱里每一层是否都拒绝修改——光靠顶层 const 关键字远远不够。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











