在现代 C++ 中实现聚合的正确方法是什么?
What is the correct way to implement Aggregation in modern C++?
上下文:
通常会出现我需要聚合的情况:一个对象使用另一个对象而不拥有它。也就是说,一些主模块将创建并共享一个对象 for/to others.
但是,到目前为止,我还没有找到正确的实现方法。
之前的研究:
1) 类似 C 的指针:
共享对象作为指针提供。问题在于程序员要正确管理创建、共享和删除的顺序,这很容易携带悬挂指针。
int main()
{
A a;
B b(&a);
return 0;
}
2)弱指针:
使用 shared/weak 指针解决悬挂指针的问题。主要问题是:它避免使用堆栈对象。它还依赖于例外情况,在最好的情况下是一个有争议的话题,在最坏的情况下是不可能的(kernel/low-level 代码)。
int main()
{
std::shared_ptr<A> a(new A());
std::weak_ptr<A> wa = a; // optional, but required to show the weak_ptr use
B b(wa);
}
3) 共享指针:
仅使用共享指针与 (2) 存在相同的问题,但另外 A 的所有权未知:这违反了多项设计准则。
4) 对象参考:
共享对对象的引用解决了悬空问题,这可能是最简单的解决方案。另一方面,它强制在构造函数中传递聚合对象,它避免了赋值运算符,并且通常限制了设计。
int main()
{
A a;
B( a );
}
5) 从不聚合,只是作为参数传递。
即使可能,这也会大大增加某些函数的参数数量。在我看来,这太复杂了。
6) 单例模式
单例允许从多个模块访问单个对象。但是,这只允许共享一个实例,并且违反了多个设计指南。
问题:
在现代 C++ 中实现聚合的正确方法是什么?
理想的目标是:
- 使代码易于维护,避免错误。
- 以不同方式使用它的灵活性(例如 stack/heap)
- 没有太多额外的复杂性或晦涩难懂的代码。
正如许多评论者所指出的,没有一种方法可以解决您的问题。然而,以 C++ 设计模式为生的人们已经对这类事情进行了长时间的讨论。对于更好地处理悬挂指针的一个很好的讨论(包括评论者之一 template <class T> using non_owning_ptr<T> = T*
的建议),我建议 Bjarne Stroustrup 的 talk on C++ core rules and guidelines, particularly the segment starting with the link given. Herb Sutter's follow-up talk 在这方面也很有用,介绍了一些关于静态分析工具的想法执行这些规则。
我知道这不是您正在寻找的神奇解决方案,但就其价值而言,它似乎是人们 更聪明 [=16] 的解决方案=]way 对 C++ 的经验比我想象的要多。老实说,这就是 Bjarne Stroustrup 最近将大部分时间花在这些规则和指南上的原因:C++ 本身不存在对这类事情的完美解决方案,但在一些限制条件下,我们可以更接近。
I require aggregation: An object make use of another object without owning it.
这不是我所熟悉的 "aggregation" 的任何定义。但是出于这个 post 的目的,我将使用它。
在 C++ 类型系统中,无法表达一个对象在不拥有对象的情况下访问该对象的能力。对于一个类型来说,实现这种关系的方法太多了,无法表达这种想法。
假设我们有一些实例 A 可以访问实例 B 但不拥有 B。那么,您如何定义这两个实例之间的关系?如果 B 在没有通知 A 的情况下被破坏,这种关系是不安全的。那么,我们如何确保这一点?
实际上有几十种方法。也许 B 保存在 shared_ptr
中,因此 A 可以在 weak_ptr
中保存其引用,从而在 B 不再存在时得到通知。也许有一些特定的对象 C 同时拥有 A 和 B,并且会确保这一点。也许代码的一般结构使得 B 不可能在 A 之前被销毁。也许 B 本身知道它何时链接到 A 并会在其析构函数中通知 A。或者代码可能很脆弱,但每个编写代码的人都非常小心,以确保 A 不会比 B 长寿。
没有单一的"correct way to implement it",因为"it"不是一回事。这是无数种可能的关系。而这些关系中的none必然是错误的;他们中的大多数甚至不一定是坏代码。它们只是类型之间的隐式关系。
库基础知识 v1 包括 observer_ptr<T>
,它只是一个指针包装器;它不传达所有权语义。但是,核心 C++ 指南建议 raw pointers be used for most of these use-without-ownership relations;相反建议用 owner<T>
.
注释确实拥有某些东西的指针
上下文:
通常会出现我需要聚合的情况:一个对象使用另一个对象而不拥有它。也就是说,一些主模块将创建并共享一个对象 for/to others.
但是,到目前为止,我还没有找到正确的实现方法。
之前的研究:
1) 类似 C 的指针: 共享对象作为指针提供。问题在于程序员要正确管理创建、共享和删除的顺序,这很容易携带悬挂指针。
int main()
{
A a;
B b(&a);
return 0;
}
2)弱指针: 使用 shared/weak 指针解决悬挂指针的问题。主要问题是:它避免使用堆栈对象。它还依赖于例外情况,在最好的情况下是一个有争议的话题,在最坏的情况下是不可能的(kernel/low-level 代码)。
int main()
{
std::shared_ptr<A> a(new A());
std::weak_ptr<A> wa = a; // optional, but required to show the weak_ptr use
B b(wa);
}
3) 共享指针: 仅使用共享指针与 (2) 存在相同的问题,但另外 A 的所有权未知:这违反了多项设计准则。
4) 对象参考: 共享对对象的引用解决了悬空问题,这可能是最简单的解决方案。另一方面,它强制在构造函数中传递聚合对象,它避免了赋值运算符,并且通常限制了设计。
int main()
{
A a;
B( a );
}
5) 从不聚合,只是作为参数传递。 即使可能,这也会大大增加某些函数的参数数量。在我看来,这太复杂了。
6) 单例模式 单例允许从多个模块访问单个对象。但是,这只允许共享一个实例,并且违反了多个设计指南。
问题:
在现代 C++ 中实现聚合的正确方法是什么?
理想的目标是:
- 使代码易于维护,避免错误。
- 以不同方式使用它的灵活性(例如 stack/heap)
- 没有太多额外的复杂性或晦涩难懂的代码。
正如许多评论者所指出的,没有一种方法可以解决您的问题。然而,以 C++ 设计模式为生的人们已经对这类事情进行了长时间的讨论。对于更好地处理悬挂指针的一个很好的讨论(包括评论者之一 template <class T> using non_owning_ptr<T> = T*
的建议),我建议 Bjarne Stroustrup 的 talk on C++ core rules and guidelines, particularly the segment starting with the link given. Herb Sutter's follow-up talk 在这方面也很有用,介绍了一些关于静态分析工具的想法执行这些规则。
我知道这不是您正在寻找的神奇解决方案,但就其价值而言,它似乎是人们 更聪明 [=16] 的解决方案=]way 对 C++ 的经验比我想象的要多。老实说,这就是 Bjarne Stroustrup 最近将大部分时间花在这些规则和指南上的原因:C++ 本身不存在对这类事情的完美解决方案,但在一些限制条件下,我们可以更接近。
I require aggregation: An object make use of another object without owning it.
这不是我所熟悉的 "aggregation" 的任何定义。但是出于这个 post 的目的,我将使用它。
在 C++ 类型系统中,无法表达一个对象在不拥有对象的情况下访问该对象的能力。对于一个类型来说,实现这种关系的方法太多了,无法表达这种想法。
假设我们有一些实例 A 可以访问实例 B 但不拥有 B。那么,您如何定义这两个实例之间的关系?如果 B 在没有通知 A 的情况下被破坏,这种关系是不安全的。那么,我们如何确保这一点?
实际上有几十种方法。也许 B 保存在 shared_ptr
中,因此 A 可以在 weak_ptr
中保存其引用,从而在 B 不再存在时得到通知。也许有一些特定的对象 C 同时拥有 A 和 B,并且会确保这一点。也许代码的一般结构使得 B 不可能在 A 之前被销毁。也许 B 本身知道它何时链接到 A 并会在其析构函数中通知 A。或者代码可能很脆弱,但每个编写代码的人都非常小心,以确保 A 不会比 B 长寿。
没有单一的"correct way to implement it",因为"it"不是一回事。这是无数种可能的关系。而这些关系中的none必然是错误的;他们中的大多数甚至不一定是坏代码。它们只是类型之间的隐式关系。
库基础知识 v1 包括 observer_ptr<T>
,它只是一个指针包装器;它不传达所有权语义。但是,核心 C++ 指南建议 raw pointers be used for most of these use-without-ownership relations;相反建议用 owner<T>
.