const成员变量必须在初始化列表中赋值,构造函数体内赋值会编译失败;真正不可变类还需禁用赋值/拷贝、避免裸指针、慎用mutable,并注意移动语义兼容性。

const 成员变量必须在初始化列表中赋值
如果在构造函数体里给 const 成员赋值,编译器会直接报错:error: uninitialized const member 或 assignment of read-only member。这是因为 const 成员一旦声明,就只能在对象创建时“一次性定死”,不能等到构造函数执行到花括号里再赋值。
正确做法是把所有 const 成员(包括 const int、const std::string、const MyData* 等)全部挪到成员初始化列表里:
class Person {
const std::string name_;
const int age_;
public:
Person(const std::string& n, int a) : name_(n), age_(a) {} // ✅ 正确
// Person(const std::string& n, int a) { name_ = n; age_ = a; } // ❌ 编译失败
};
- 即使
name_是const std::string,也能用std::string类型参数初始化——只要std::string有接受该类型的构造函数(比如const char*或std::string&&) - 如果成员是自定义类且含
const成员,它自己也必须靠初始化列表完成构造,否则整个链路会断掉 - 不要试图用
const_cast绕过——那不是不可变,是自欺欺人,还可能触发未定义行为
禁止提供非 const 的 setter 和拷贝赋值运算符
光靠 const 成员还不够。如果类暴露了 set_name() 这样的方法,或者没禁用 operator=,外部仍能间接修改状态。真正的不可变类必须显式封死这些出口。
常见疏漏点:
- 忘记将
operator=和拷贝构造函数设为delete,导致Person p1; Person p2 = p1;后还能通过p2修改(如果内部有指针或引用) - 返回内部
const成员的非常量引用(如const std::string& name() { return name_; }是安全的;但std::string& name()就彻底破防) - 对
const指针成员(如const Data* data_)不做深拷贝控制,导致多个对象共享同一块可变内存
推荐写法:
class ImmutableVec {
const std::vector<int> data_;
public:
ImmutableVec(std::vector<int> v) : data_(std::move(v)) {}
const std::vector<int>& get() const { return data_; }
ImmutableVec& operator=(const ImmutableVec&) = delete;
ImmutableVec(const ImmutableVec&) = default; // 可以允许拷贝(深拷贝已由 vector 完成)
};</int></int></int>
注意 const 成员的“浅不可变”陷阱
const 修饰的是成员变量本身,不是它指向的内容。例如 const std::unique_ptr 表示指针不可变(不能指向别处),但 *ptr 仍可被修改;而 std::unique_ptr 才真正禁止修改所指对象。
常见误判场景:
-
const std::shared_ptr→ptr_; ptr_.reset()不行,但ptr_->mutate()依然合法 -
const std::vector<:string>* ptr_;→ 指针不可变,但ptr_->push_back("x")仍能改内容 - 含
mutable成员时,即使整个对象是const,mutable成员仍可修改(常用于缓存),这与“不可变”语义冲突,应避免
若需真正意义上的不可变数据结构,优先用值语义(如 std::vector、std::string)而非裸指针或引用;必须用指针时,确保类型是 std::unique_ptr 或 const T* 并配合适当构造逻辑。
移动语义与 const 成员的兼容性
带 const 成员的类默认无法生成移动构造函数和移动赋值运算符(因为移动操作本质是“掏空原对象”,而 const 成员不允许被修改)。编译器会自动退回到拷贝——这可能导致意外的性能开销。
解决方式只有手动定义移动操作,并在初始化列表中“转移”const 成员:
class ImmutableBlob {
const std::vector<char> data_;
public:
ImmutableBlob(std::vector<char>&& v) : data_(std::move(v)) {}
ImmutableBlob(ImmutableBlob&& other) noexcept : data_(std::move(other.data_)) {}
ImmutableBlob& operator=(ImmutableBlob&&) = delete; // 通常禁用移动赋值,保持不可变语义
};</char></char>
-
std::move在初始化列表中对const成员有效,前提是该成员类型的移动构造函数接受const T&&(标准容器都支持) - 移动后,源对象的
data_仍为const,但其内部缓冲区已被掏空(如vector变为空)——这是安全的,因你不再依赖源对象的状态 - 不要给不可变类实现移动赋值:它违背“构造即终态”的设计前提
最易被忽略的一点:不可变不等于只读视图。真正可靠的 Immutable 类,必须从内存布局、接口契约、所有权语义三方面同时守住防线——少一个,就只是纸糊的 const。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











