关于Null Object模式下Getter两种实现方式的技术疑问
关于Null Object模式中Getter实现的两种方案分析
嘿,这个问题戳中了Null Object模式里很容易忽略的细节——毕竟虽然我们都知道要尽量避免Getter,但真遇到必须用的场景,返回值的处理确实得仔细权衡。咱们来拆解你提到的第一种实现,再聊聊另一种更常见的优化方案,以及各自的优劣。
第一种实现:按值返回空容器
你给出的代码大致是这样的(我补全了细节让逻辑更完整):
// 假设SomeObject是已定义的业务类型 class NullObject { public: std::vector<SomeObject> get() { return {}; } }; class SomeImplementation { private: std::vector<SomeObject> someVectorMember; public: std::vector<SomeObject> get() { return someVectorMember; } }; // 调用方式 const std::vector<SomeObject>& object = instance.get();
核心细节:临时对象的生命周期
这里有个关键的C++规则:当你把临时对象绑定到const左值引用上时,这个临时对象的生命周期会被延长到引用的生命周期结束。所以你的调用方式是安全的,不会出现悬空引用的问题。
但这种方式有个明显的缺点:每次调用NullObject::get()都会创建一个新的临时空vector。虽然空vector的构造、析构开销极小(大多数实现里空vector只存几个指针,没有堆内存分配),但如果是高频调用的场景,累积起来的开销还是能感知到的。另外,SomeImplementation::get()按值返回成员的话,若容器里存了大量元素,拷贝开销会非常大,这显然不是最优选择。
第二种实现:返回静态空容器的const引用
这是更常用的优化方案,把Null Object的返回值改成静态常量的引用,同时让真实实现也返回成员的const引用,保持接口完全一致:
class NullObject { public: const std::vector<SomeObject>& get() { // 静态局部变量,程序生命周期内仅初始化一次 static const std::vector<SomeObject> emptyVector{}; return emptyVector; } }; class SomeImplementation { private: std::vector<SomeObject> someVectorMember; public: const std::vector<SomeObject>& get() const { // 加const保证成员不被意外修改 return someVectorMember; } }; // 调用方式不变 const std::vector<SomeObject>& object = instance.get();
优势
- 性能最优:静态空容器只会被初始化一次,后续调用都直接返回它的引用,没有临时对象的构造/拷贝开销;真实实现返回成员的引用,也避免了大容器的拷贝。
- 接口一致性:两种实现的
get()返回类型完全一致,调用方不需要区分当前实例是Null Object还是真实实现,完美符合Null Object模式“透明处理空对象”的设计初衷。
需要注意的坑
- 必须返回
const引用:如果返回非const引用,有人不小心修改了这个静态空容器,所有使用Null Object的地方都会受到影响,完全违背了Null Object的语义。 - 静态变量的线程安全:C11及以后的标准保证,静态局部变量的初始化是线程安全的(多线程第一次调用时不会出现竞态条件),但如果项目还在使用老版本编译器(比如C03及之前),需要自行加锁保证线程安全。
两种方案的选择建议
- 如果
get()调用频率不高,或者容器本身很小,第一种按值返回的方案足够简单,不需要考虑静态变量的初始化问题,代码可读性也更好。 - 如果是高频调用场景,或者容器可能很大,第二种返回const引用的方案显然更合适,能有效避免不必要的性能开销。
- 无论选哪种,一定要保证两个类的
get()接口完全一致——要么都返回const引用,要么都按值返回,这样调用方才能无缝处理Null Object和真实实现,不用额外做判断。
内容的提问来源于stack exchange,提问作者Newbie
相关产品推荐
相关产品推荐

