C++ 返回默认值,NullObjectPattern

C++ returning default value, NullObjectPattern

我有一个关于某种空对象模式的问题。 当我想到 getter 时(我知道我们应该避免这种情况,但假设) 我看到了 2 种方法。

假设我们有一个classNullObject.cpp

1)

class NullObject
{
    std::vector<SomeObject> get() { return {}; }
}

class SomeImplementation
{
     std::vector<SomeObject> get() { return someVectorMember; }
{

const std::vector<SomeObject>& object = instance.get();

因此在第一个示例中,我们将始终按值 returning 并分配给 const Object&

2)

class NullObject
{
    const std::vector<SomeObject>& get() { return member; }

    static std::vector<SomeObject> member;
}

class SomeImplementation
{
     const std::vector<SomeObject>& get() { return someVectorMember; }
{

const std::vector<SomeObject>& object = instance.get();

在这种情况下,我们在 Null class 中有静态成员,因此我们可以 return 一个 const 引用。

问题:比如在性能方面哪个更好? "clean" 代码哪个更好? 有没有(更好的)选择? 也许我的例子有误?

谢谢

TL;DR:这取决于您的应用程序的上下文,但是在使用第二种技术时您应该考虑一些严重的安全问题。


在这种情况下,更好是主观的,因为它取决于上下文,但有一些 objective trade-offs 和安全问题可以区分这两种技术。

在第一种情况下,您为每次调用创建了完全不同的对象。这需要使用一些循环来实例化对象。根据对象的复杂性,这可能是微不足道的,也可能非常昂贵。例如,如果对象的构造函数通过网络进行调用并阻塞,则连续构造这些对象可能是一个糟糕的性能决策。这些是否是严重的性能问题取决于使用它的上下文。例如:

  • 在系统的整个生命周期中,此方法平均被调用多少次?
  • returned 对象在范围内停留多长时间,因此占用堆栈 space?
  • 堆栈 space 在此特定应用程序中有多大价值?这种方法可以说更简单,因为它不需要将字段(无论是 class 还是静态的)添加到 class.

第二种方法节省了每次调用时的构造开销(在初始化静态变量时执行一次),但它有一些严重的安全问题,应该考虑。对象 returned 由调用此方法的所有客户端共享,如果某些实体更改了共享对象的值,这可能会成为一个问题。尽管共享对象被 return 编辑为 const 引用,但这并不意味着对象的值不能改变(即 SomeObject 对象的内部字段可以改变)。有三种直接的方法来改变值,即使它被 returned 为 const(如果使用更多的侵入性手段,还有更多):

  1. NullObject 更改值。此共享对象相对于 NullObject 不是 const,因此,如果 SomeObject 是可变的,NullObject 可以更改值 SomeObject。如果进行了更改,则所有获得此共享对象引用的客户端都将看到此更改。如果对象确实是不可变的(参见 (2) 和 (3) 了解不可变性的方法),那么就没有什么可担心的,因为共享对象的值一旦被实例化就不能改变,但如果对象是可变的,它的值可以更改,并且所有获得共享对象的客户端都可以看到该更改。
  2. 即使对象是 const,声明的字段 mutable 也会更改。使用该对象的客户端不知道其内部结构(即封装),因此他们可能不知道 SomeObject 中有标记为 mutable 的内部字段并且可能会更改,即使 returned 对象是 const。例如:

    class SomeObject {
    
        public: void doSomething() const {
            // Do some logic
            this->count++;
        }
    
        public: const int& getCounter() const {
            return this->counter;
        }
    
        private: mutable int counter = 0;
    }
    

    这可能是错误的形式,这是可能的,因此,必须加以考虑。

  3. return 值的常量性被抛弃了。例如:

    std::vector<SomeObject>& mutable_vector = const_cast<std::vector<SomeObject>&>(instance.get());
    

考虑到这三个问题,重要的是要非常准确地了解 C++ 中共享的对象类型,即使它们表示为 const。尽管 const 关键字可能看起来提供了不变性,但也有一些方法可以解决这种不变性,其中一些方法可以由客户端执行。

一般来说,每个解决方案的性能将取决于您的应用程序的上下文,您应该使用这两种技术进行性能测试,以了解在计算方面对您的应用程序的影响(需要多长时间才能完成)执行)、内存消耗(执行时占用多少空间),以及与您的上下文相关的许多其他因素。这个问题也相当于一个更普遍的问题:何时缓存值。在这种特殊情况下,该值旨在保持不变,因此您不必担心生存时间 (TTL)、意义过期或陈旧数据,但某些概念确实可以转换,例如 "is it worth it to introduce the complexity of caching for the benefit of performance?"