How to implement class composition in C++?

2019-04-08 06:02发布

问题:

If I understand correctly we have at least two different ways of implementing composition. (The case of implementation with smart pointers is excluded for simplicity. I almost don't use STL and have no desire to learn it.)

Let's have a look at Wikipedia example:

class Car
{
  private:
    Carburetor* itsCarb;
  public:   
    Car() {itsCarb=new Carburetor();}
    virtual ~Car() {delete itsCarb;}
};

So, it's one way - we have a pointer to object as private member. One can rewrite it to look like this:

class Car
{
  private:
    Carburetor itsCarb;
};

In that case we have an object itself as private member. (By the way, am I right to call this entity an object from the terminology point of view?)

In the second case it is not obligatory to implicitly call default constructor (if one need to call non-default constructor it's possible to do it in initializer list) and destructor. But it's not a big problem...

And of course in some aspects these two cases differ more appreciably. For example it's forbidden to call non-const methods of Carburetor instance from const methods of Car class in the second case...

Are there any "rules" to decide which one to use? Am I missing something?

回答1:

In that case we have an object itself as private member. (By the way, calling this entity as object am I write from the terminology point of view?)

Yes you can say "an object" or "an instance" of the class.

You can also talk about including the data member "by value" instead of "by pointer" (because "by pointer" and "by value" is the normal way to talk about passing parameters, therefore I expect people would understand those terms being applied to data members).

Is there any "rules" to decide which one to use? Am I missed something?

If the instance is shared by more than one container, then each container should include it by pointer instead of value; for example if an Employee has a Boss instance, include the Boss by pointer if several Employee instances share the same Boss.

If the lifetime of the data member isn't the same as the lifetime of the container, then include it by pointer: for example if the data member is instantiated after the container, or destroyed before the container, or destroyed-and-recreated during the lifetime of the container, or if it ever makes sense for the data member to be null.

Another time when you must including by pointer (or by reference) instead of by value is when the type of the data member is an abstract base class.

Another reason for including by pointer is that that might allow you to change the implementation of the data member without recompiling the container. For example, if Car and Carburetor were defined in two different DLLs, you might want to include Carburetor by pointer: because then you might be able to change the implementation of the Carburetor by installing a different Carburetor.dll, without rebuilding the Car.dll.



回答2:

I tend to prefer the first case because the second one requires you to #include Carburettor.h in Car.h. Since Carburettor is a private member you should not have to include its definition somewhere else than in the actual Car implementation code. The use of the Carburettor class is clearly an implementation detail and external objects that use your Car object should not have to worry about including other non mandatory dependencies. By using a pointer you just need to use a forward declaration of Carburettor in Car.h.



回答3:

Composition: prefer member when possible. Use a pointer when polymorphism is needed or when a forward declaration is used. Of course, without smart pointer, manual memory management is needed when using pointers.



回答4:

If Carb has the same lifetime as Car, then the non-pointer form is better, in my opinion. If you have to replace the Carb in Car, then I'd opt for the pointer version.



回答5:

Generally, the non-pointer version is easier to use and maintain.

But in some cases, you can't use it. For example if the car has multiple carburetors and you wish to put them in an array, and the Carburetor constructor requires an argument: you need to create them via new and thus store them as pointers.



标签: c++ oop