设计类聚合 - 堆栈分配与动态内存分配

时间:2010-11-23 11:52:18

标签: c++ class-design

请看下面设计类聚合的两个简化示例。

解决方案1 ​​

标题

// need include, forward declaration is not enough
#include "door.h"

class CGarage
{
public:
    CGarage(const std::string &val);

private:
    CDoor m_door;
};

来源

#include "garage.h"
CGarage::CGarage(const std::string &val)
        :m_door(val)
{
}

解决方案2

标题

#include "smart_ptr.hpp"

// forward declaration
class CDoor;

class CGarage
{
public:
    CGarage(const std::string &val);

private:
    scoped_ptr<CDoor> m_door;
};

来源

#include "garage.h"
#include "door.h"

CGarage::CGarage(const std::string &val)
        :m_door(new CDoor(val))
{
}

有关创建CDoor成员的问题

您在示例设计中看到了哪些优点/缺点(CDoor与自动分配的动态分配)?

这就是我提出的:

解决方案1:
+没有内存处理或寿命问题 +在运行时不需要昂贵的内存分配
- 在标题中需要额外的包含(编译速度更慢?,更接近CDoor) - &gt;头文件中的许多包含被认为是坏的...

解决方案2:
+标题中带有CDoor的松散耦合(仅需要前向声明)
- 内存需要由程序员处理

您通常喜欢哪种设计?

7 个答案:

答案 0 :(得分:8)

我们很少得到问题设计(我的意思是,有趣的)。

让我们暂时忘掉(明显)人为的例子并专注于这个概念。

我们有两个解决方案:

  • 包含:拉入标题并直接构建对象
  • 包含:forward声明标头并使用指针

我会暂时放弃所有“表演”的论点。性能在97%的时间内无关紧要(Knuth说),除非我们测量显着的差异,因为功能相同,所以我们现在不必担心它。

因此,我们有两个正交的概念试图影响我们的决定:

  • 依赖让我们倾向于 Soft 遏制
  • 简单让我们倾向于 Hard 遏制

这里的一些答案正确地谈到了多态性,但Door的确切实现是Door关注的细节,而不是Garage。如果Door希望提供多种实现,那就没关系,只要客户不需要关注这些细节。

我自己也是KISS和YAGNI原则的粉丝。所以我会支持 Hard 遏制... 一个警告

当设计一个将要公开的接口时,一个接口位于库的前沿,那么这个接口应该暴露最少的依赖和内部。理想情况下,这应该是FacadeProxy,这个对象的唯一目的是隐藏库的内部,并且此对象在其标题中应具有最小的依赖性并具有最大的布局兼容性,的意思是:

  • 无虚拟方法
  • 一个简单的指针作为属性(Pimpl)

对于所有内部课程,简单性取胜。

答案 1 :(得分:2)

在所有可能的情况下,解决方案1在运行和编译时都是优越的,除非您遇到包含依赖项的极端问题,并且必须采取行动来减少它们。解决方案2有比你提到的更多的问题 - 你需要编写和维护额外的复制构造函数/赋值运算符,只是为了开始。

答案 2 :(得分:1)

这不仅是一个耦合问题(实际上远非如此:如果使用多态,动态分配会变得非常有趣)。经典的经验法则是:如果你能在课堂上拥有这个对象,那就去做吧。它与函数完全相同:如果你可以拥有一个局部变量,那么就不要为了噩梦般的调试而分配内存。

例如,如果您需要聚合未知数量的组件,指针(共享,智能或哑)是您的朋友。例如,如果你不知道你的车库将有多少门,指针(实际上,不是共享的)和动态分配是一个好主意。

如果你有另一个对象使用的对象,它总是属于同一个类,并且在它的所有者死了之后没用,为什么你需要经历动态分配?

简而言之:上下文就是一切,但是,为了您自己,尝试尽可能少的动态对象。

答案 3 :(得分:1)

对我来说,这些设计是等效的。在每种情况下,CDoor都归CGarage所有。

我更喜欢1.因为第二个shared_ptr似乎没有添加任何东西,只有复杂性 - 谁CGarage与之共享?你对1的缺点对我没有吸引力。

为什么不在{2}中使用scoped_ptr,除非你为CDoor对象提供了一个getter?

答案 4 :(得分:0)

解决方案1:

您正在公开Door的标题,这只是您班级的实施细节。如果Door是Garage公共界面的一部分,你可能会认为Garage的用户也会使用Door,但是如果它是私有的,最好不要暴露。

解决方案2:

使用shared_ptr意味着如果您复制车库,则您的副本具有相同的门。不仅仅是类似的,同一个。如果你在你的一个车库上把门涂成绿色,那么你的车库都会有绿色的门。你必须明白这个问题。

您的课程在您的代码中所处的位置对于哪个更适合使用起着重要作用。如果Garage是你的公共接口的一部分而且Door根本不在公共接口的任何地方,那么将它解耦是非常有益的,可能使用shared_ptr。

如果Garage不是你的公共接口的一部分,但是是一个实现细节,我不会太在意耦合问题。

如果Garage和Door都在您的公共界面中。和Door非常常用于Garage,而Door.h并没有带来更多的标题但是相当轻,你可以将聚合作为一个对象(包括标题)。

答案 5 :(得分:0)

除非两个车库共用同一扇门,否则解决方案#1作为shared_ptr会给人一种分享门的印象。

答案 6 :(得分:0)

还需要考虑更多要点:

解决方案1(假设CDoor不是指针类型的typedef):

  • 不是“多态友好”,因为您将在初始化时按值复制对象(即使您通过引用传递)。请参阅“课程切片”问题:What is object slicing?
  • 您无法实现用于快速处理/初始化CGarage的pimpl习惯用法

一般来说,(1)意味着CGarage与CDoor紧密相关。当然,如果CDoor是某种适配器/装饰器,你可以获得更多的灵活性

解决方案2:

  • 课程耦合不太紧密
  • 昂贵的堆分配
  • 智能指针的额外费用

这两种设计都不是首选的“通常”,这完全取决于您的班级建模的内容,以及它将如何使用。

如果a可能会进一步建议你,请研究“C ++设计模式”,以获得更多的见解。

这对初学者应该是好的: