Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

design class aggregation - stack allocation vs dynamic memory allocation

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?

like image 793
nabulke Avatar asked Nov 23 '10 11:11

nabulke


2 Answers

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.

like image 96
Matthieu M. Avatar answered Oct 13 '22 23:10

Matthieu M.


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.

like image 2
Puppy Avatar answered Oct 14 '22 01:10

Puppy