Search code examples
c++pointersreferenceshared-ptrmember-variables

Objects as member variables in a class in C++


I'd like to know what are the best practice to handle object instances as member variables of another class. After reading different posts it seems that, in general, having references to objects as member variables should be avoided, but I'm not sure if using pointers is a good solution. Another possibility is to have const references as parameter of the class constructor and then use the copy constructor to initialize the member variables.

  • What are the rule of thumbs to handle this situation?
  • What about using share_ptr as member variables?
  • What if the member variable refers to an abstract class? In this case, are there other alternatives to pointers or references?

EDIT:

Since the answer to the question can really depends on the context, I'll specify it in the case when a class member refers to an abstract class. As an example, consider the following code that implements the strategy pattern:

 class Client{

        // reference to Abstract strategy
        void execute(){
             strategy.doStuff();
        }
 }

 class AbstractStrategy{

 public:
      virtual void doStuff() = 0;
 }

 class ConcreteStrategy{
      void doStuff(){}
 }

Since AsbtractStrategy has pure virtual functions, it seems that the only way to have it as a member of Client is using pointers or references. Are there better solutions?


Solution

  • There is certainly nothing wrong with (even raw) pointers. You just have to be responsible. Have the member implemented via a pointer if you want the object it references to change, otherwise it is pointless to add another level of indirection, it will be more efficient both in terms of memory usage and CPU time to have it as a regular member variable.

    A Person class should have its age member as a number, not as a pointer to one, but a person's residence might be a pointer, to reference an object in a collection of residences. And it is not always necessary that the person should manage a residence or use smart pointers, the residence data layer might be entirely independent on the person layer, it would make little sense for a residence to be deleted together with a person, instead it should just become vacant.

    It all depends on what you need, does the object need to be something, to have something or merely to reference something external.

    Keep in mind indirection comes at a cost. Which is why it makes little sense to have an age member implemented as a pointer, people don't live very long, you can get away with a single byte to store the age, and you will gain nothing if you have a collection of ages and you reference one for each person, because a pointer will typically be 4 or 8 bytes, plus the byte it references, you waste memory and CPU time to retrieve the data in that memory address. But the residence might be a big objects, plus it is not tightly coupled with a person, so it makes sense for a person's residence to be implemented as a pointer. And while a smart pointer will delete a residence as the person is deleted, a regular pointer will just allow you to unregister that person from the residence's inhabitant references in the person's destructor.

    Also consider this - you might have abstraction between objects, for example you may not really want to have each person have a residence pointer, or multiple in the case of multiple residences, in fact a person might know nothing about the existence of a residence, you can still establish relation between a person and a residence by the use of a third object without keeping such as members, for example a map of object and residence pointers, so you can get a person's residence(s) if any by querying the map, it will be slower than having a residence pointer for every person, but it will save on memory from not storing residence pointer for every instance of person. which may not have a residence at all.

    As for abstract classes - since those cannot be instantiated, you cannot have one as a member object. And since the idea is to define an interface, you would most likely be a pointer and make use of virtual dispatch and polymorphism. Note that aside from aggregation you can also use inheritance. If there is a single strategy per object, and every object has it, then both aggregation and inheritance will do the trick. In the case of some objects having multiple and others having none, you could go for the decoupled design, mentioned in the previous paragraph. Also note that using inheritance Strategy::doStuff() will become part of that object's vtable, meaning every object of that particular type will end up with the same code being executed, while using aggregation or other coupling, the strategy can be set on a per instance base.