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
(如果使用更多的侵入性手段,还有更多):
-
NullObject
更改值。此共享对象相对于 NullObject
不是 const
,因此,如果 SomeObject
是可变的,NullObject
可以更改值 SomeObject
。如果进行了更改,则所有获得此共享对象引用的客户端都将看到此更改。如果对象确实是不可变的(参见 (2) 和 (3) 了解不可变性的方法),那么就没有什么可担心的,因为共享对象的值一旦被实例化就不能改变,但如果对象是可变的,它的值可以更改,并且所有获得共享对象的客户端都可以看到该更改。
即使对象是 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;
}
这可能是错误的形式,这是可能的,因此,必须加以考虑。
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?"
我有一个关于某种空对象模式的问题。 当我想到 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
(如果使用更多的侵入性手段,还有更多):
-
NullObject
更改值。此共享对象相对于NullObject
不是const
,因此,如果SomeObject
是可变的,NullObject
可以更改值SomeObject
。如果进行了更改,则所有获得此共享对象引用的客户端都将看到此更改。如果对象确实是不可变的(参见 (2) 和 (3) 了解不可变性的方法),那么就没有什么可担心的,因为共享对象的值一旦被实例化就不能改变,但如果对象是可变的,它的值可以更改,并且所有获得共享对象的客户端都可以看到该更改。 即使对象是
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; }
这可能是错误的形式,这是可能的,因此,必须加以考虑。
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?"