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

发布于 2024-10-03 14:40:11 字数 1115 浏览 6 评论 0原文

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

解决方案 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 的耦合更紧密)->头文件中的许多包含被认为是不好的...

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

您通常出于什么原因更喜欢哪种设计?

Please have a look at the two simplified examples of designing a class aggregation below.

Solution 1

Header

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

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

private:
    CDoor m_door;
};

Source

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

Solution 2

Header

#include "smart_ptr.hpp"

// forward declaration
class CDoor;

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

private:
    scoped_ptr<CDoor> m_door;
};

Source

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

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

Questions concerning the creation of the CDoor member

What advantages/disadvantages do you see in the design of the examples (dynamic allocation of CDoor vs automatic allocation)?

This is what I came up with:

Solution 1:
+ no issues with memory handling or lifetime
+ no need for expensive memory allocation at runtime
- need additional include in header (compilation speed slower?, closer coupling to CDoor) -> many includes in header files are considered bad...

Solution 2:
+ loose coupling with CDoor in header (only forward declaration needed)
- memory needs to be handled by programmer

Which design do you usually prefer for what reason?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

随心而道 2024-10-10 14:40:11

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

让我们暂时忘记(显然)人为的例子,集中精力讨论这个概念。

我们有 2 个解决方案:

  • 硬遏制:拉入标头并直接构建对象
  • 遏制:前向声明标头并使用指针

我会自愿放弃所有“表演” ”暂时争论。性能在 97% 的时间里并不重要(Knuth 说),因此除非我们测量明显的差异,因为功能是相同的,所以我们目前不必担心它。

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

  • 依赖使我们倾向于遏制
  • 简单使我们倾向于硬< /em> 遏制

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

我本人是 KISS 和 YAGNI 原则的忠实粉丝。因此,我主张采用硬性收容......但有一个警告

当设计一个将要公开的接口时,一个位于库前沿的接口,那么这个接口应该公开最少的依赖关系和内部结构。理想情况下,这应该是一个Facade或一个Proxy,一个唯一目的是隐藏库内部的对象,并且该对象在其标头和具有最大的布局兼容性,这意味着:

  • 没有虚拟方法
  • 作为属性的简单指针 (Pimpl)

对于所有内部类,简单性胜出。

It is rare that we get question design (I mean, interesting ones).

Let's forget for a moment the (obviously) contrived example and concentrate on the notion.

We have 2 solutions:

  • Hard containment: pull in the header and build the object directly
  • Soft containment: forward declare the header and use a pointer

I'll voluntarily discard all "performances" argument for the moment. Performance doesn't matter 97% of the time (says Knuth) so unless we measure a noticeable difference, since the functionality is identical, we thus need not worry about it at the moment.

We therefore have two orthogonal concepts attempting to sway our decision:

  • Dependency make us lean toward Soft containment
  • Simplicity make us lean toward Hard containment

Some answers here have rightly spoken about polymorphism, but the exact implementation of Door is a detail that is Door's concern, not Garage's. If Door wishes to offer several implementations, that's fine, as long as its clients need not be concerned by this detail.

I am quite a fanboy, myself, of the KISS and YAGNI principles. So I would argue in favor of Hard containment... with one caveat.

When designing an interface that will be exposed, an interface therefore that stands at the frontier of the library, then this interface should expose a minimum of dependencies and internals. Ideally, this should be a Facade or a Proxy, an object whose only purpose is to hide the internals of the library, and this object should have minimal dependencies in its header and have maximal layout compatibility, which means:

  • no virtual method
  • a simple pointer as an attribute (Pimpl)

For all internal classes, simplicity wins hands off.

拥抱我好吗 2024-10-10 14:40:11

在每种可能的情况下,解决方案 1 在运行时和编译时都表现出色,除非您在包含依赖项方面遇到极端问题并且必须采取措施减少这些问题。解决方案 2 的问题比您提到的更多 - 您需要编写和维护额外的复制构造函数/赋值运算符,这只是开始。

Solution 1 is superior at both run and compile-time in every conceivable case, unless you're having extreme issues with include dependencies and must act to reduce them. Solution 2 has more issues than you've mentioned - you'll need to write and maintain additional copy constructor/assignment operator, just to begin with.

请持续率性 2024-10-10 14:40:11

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

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

为什么不在 2. 中使用 scoped_ptr ,除非您为 CDoor 对象提供 getter?

To me these designs are equivalent. In each case CDoor is owned by CGarage.

I prefer 1. since the shared_ptr in the second does not seem to add anything but complexity - who is CGarage sharing it with? Your cons for 1. are not compelling to me.

Why not use scoped_ptr in 2. unless you are providing a getter for the CDoor object?

初吻给了烟 2024-10-10 14:40:11

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

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

如果你有一个对象被另一个对象使用,那么它总是属于同一类,并且在它的所有者死亡后就没有用了,那么你到底为什么需要进行动态分配呢?

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

It's not only a question of coupling (far from it, actually : dynamical allocation becomes really interesting if you use polymorphism). The classical rule of thumb, there, is : if you can have the object in your class, do it. It's exactly the same thing as in a function : if you can have a local variable, take it, don't go allocating memory for the sake of nightmarish debugging.

For instance, if your going to need an aggregation of an unknown numbers of components, pointers (shared, smart, or dumb) are your friends. Here, for instance, if you don't know how much doors your garage is going to have, pointers (actually, not shared ones) and dynamical allocation is a good idea.

If you have an object used by another object, that is always going to be of the same class, and that is not useful after it's owner is dead, why on earth would you need to go through dynamic allocation ?

In short : the context is everything, but, for your own sake, try to have as few dynamic object as possible.

长不大的小祸害 2024-10-10 14:40:11

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

Unless two garages shares the same door, solution #1 as a shared_ptr gives the impression the door is shared.

国产ˉ祖宗 2024-10-10 14:40:11

解决方案 1:

您公开 Door 的标头,它只是您的类的实现细节。如果 Door 是 Garage 公共接口的一部分,您可能会认为 Garage 的用户也会使用 Door,但如果它是私有的,最好不要暴露。

解决方案2:

使用shared_ptr,这意味着如果您复制车库,您的副本具有相同的门。不只是相似,而是相同。如果您将其中一个车库的门漆成绿色,那么您的两个车库都会有绿色的门。这个问题你一定要明白。

您的类在代码中的位置对于使用哪个类更好起着重要作用。如果 Garage 是公共接口的一部分,而 Door 根本不在公共接口中的任何位置,那么将其解耦(可能使用 shared_ptr)非常有益。

如果 Garage 不是任何地方公共接口的一部分,而是一个实现细节,我就不会太关心耦合问题。

如果车库和门都在您的公共界面中。 Door 非常常与 Garage 一起使用,并且 Door.h 不会引入更多标头,但相当轻,您可以将聚合作为对象(包括标头)。

Solution 1:

You are exposing the header of Door which is only an implementation detail of your class. If Door was part of the public interface of Garage you might assume users of Garage are going to use Door too, but where it is private, it is far better not to be exposed.

Solution 2:

Using shared_ptr it means if you copy a Garage your copy has the same door. Not just as similar one, the same one. If you paint the door green on one of your garages, both your garages will have green doors. You must understand that issue.

Where your class sits in your code plays a major part as to which is better to use. If Garage is part of your public interface and Door is not anywhere in the public interface at all then it is very much beneficial to decouple it out, possibly using shared_ptr.

If Garage is not part of your public interface anywhere but is an implementation detail I would not care so much about the coupling issue.

If Garage and Door are both in your public interface. and Door is very commonly used with Garage,.and Door.h does not bring in even more headers but is fairly light, you can get away with the aggregation as an object (include the header).

执手闯天涯 2024-10-10 14:40:11

还有一些需要考虑的要点:

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

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

一般来说,(1) 意味着 CGarage 与 CDoor 紧密兼容。当然,如果 CDoor 是某种适配器/装饰器,您可以实现更多的灵活性

解决方案 2:

  • 类耦合不太紧密 昂贵的
  • 堆分配
  • 智能指针的额外成本

“通常”这两种设计都不是首选,这完全取决于您的类建模是什么它负责什么以及如何使用它。

如果可以为您提供进一步的建议,请研究“C++ 设计模式”主题以获得更多见解。

这些对于初学者来说应该很好:

Some more points to consider:

Solution 1 (assuming that CDoor is not a typedef for a pointer type):

  • Is not "polymorphism friendly" since you will copy objects by value on initialization (even if you pass by reference). Please see "class slicing" issue: What is object slicing?
  • You can not implement pimpl idiom for fast coping/initialization of CGarage

In general, (1) means that CGarage is tightly compled with CDoor. Of course, you can achieve some more flexiblity if CDoor is some kind of adapter/decorator

Solution 2:

  • Classes coupled less tightly
  • Expensive heap allocation
  • Additional costs for smart pointer

Neither design can be preferred "usually" this depends entirely on what is your class modeling what it's responsible for and how it will be used.

If a may advise you further, please research "C++ design patterns" subject to get some more insight.

Those should be good for starters:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文