Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Avoid returning handles to object internals", so what's the alternative?

Effective C++ by Scott Meyers tells in Chapter 5, Item 28 to avoid returning "handles" (pointers, references or iterators) to object internals and it definitely makes a good point.

I.e. don't do this:

class Family
{
public:
    Mother& GetMother() const;
}

because it destroys encapsulation and allows to alter private object members.

Don't even do this:

class Family
{
public:
    const Mother& GetMother() const;
}

because it can lead to "dangling handles", meaning that you keep a reference to a member of an object that is already destroyed.

Now, my question is, are there any good alternatives? Imagine Mother is heavy! If I now return a copy of Mother instead of a reference, GetMother is becoming a rather costly operation.

How do you handle such cases?

like image 544
schluchc Avatar asked Nov 01 '12 12:11

schluchc


2 Answers

First, let me re-iterate: the biggest issue is not one of lifetime, but one of encapsulation.

Encapsulation does not only mean that nobody can modify an internal without you being aware of it, encapsulation means that nobody knows how things are implemented within your class, so that you can change the class internals at will as long as you keep the interface identical.

Now, whether the reference you return is const or not does not matter: you accidentally expose the fact that you have a Mother object inside of your Family class, and now you just cannot get rid of it (even if you have a better representation) because all your clients might depend on it and would have to change their code...

The simplest solution is to return by value:

class Family {
public:

    Mother mother() { return _mother; }
    void mother(Mother m) { _mother = m; }

private:
    Mother _mother;
};

Because in the next iteration I can remove _mother without breaking the interface:

class Family {
public:

     Mother mother() { return Mother(_motherName, _motherBirthDate); }

     void mother(Mother m) {
         _motherName = m.name();
         _motherBirthDate = m.birthDate();
     }

private:
     Name _motherName;
     BirthDate _motherBirthDate;
};

See how I managed to completely remodel the internals without changing the interface one iota ? Easy Peasy.

Note: obviously this transformation is for effect only...

Obviously, this encapsulation comes at the cost of some performance, there is a tension here, it's your judgement call whether encapsulation or performance should be preferred each time you write a getter.

like image 52
Matthieu M. Avatar answered Sep 17 '22 10:09

Matthieu M.


Possible solutions depend on actual design of your classes and what do you consider "object internals".

  1. Mother is just implementation detail of Family and could be completely hidden from Family user
  2. Family is considered as composition of other public objects

In first case you shall completely encapsulate subobject and provide access to it only via Family function members (possibly duplicating Mother public interface):

class Family
{
  std::string GetMotherName() const { return mommy.GetName(); }
  unsigned GetMotherAge() const { return mommy.GetAge(); }
  ...
private:
   Mother mommy;
   ...
};

Well, it can be boring if Mother interface is quite large, but possibly this is design problem (good interfaces shall have 3-5-7 members) and this will make you revisit and redesign it in some better way.

In second case you still need to return entire object. There are two problems:

  1. Encapsulation breakdown (end-user code will depend on Mother definition)
  2. Ownership problem (dangling pointers/references)

To adress problem 1 use interface instead of specific class, to adress problem 2 use shared or weak ownership:

class IMother
{
   virtual std::string GetName() const = 0;
   ...
};

class Mother: public IMother
{
   // Implementation of IMother and other stuff
   ...
};

class Family
{
   std::shared_ptr<IMother> GetMother() const { return mommy; }
   std::weak_ptr<IMother> GetMotherWeakPtr() const { return mommy; }

   ...
private:
   std::shared_ptr<Mother> mommy;
   ...
};
like image 32
Rost Avatar answered Sep 18 '22 10:09

Rost