Returning handles to private members makes them public

Some key features of object-oriented programming can be brunch together to something I want to call the “black box idea”. An object is a black box offering a public interface. All internal implementations, like state management, data control and resource management are not visible to the client. That’s why we implement a public interface and private class members.

Within this article I want to think about the question whether we can return handles to class internal members with respect to the black box idea or whether this will violate the object oriented principles.

At first we will create a small example application which we will later on use to test several possibilities to return handles. Within the example we have a class “Person” which should manage person specific data. The data is stored in a struct and the person class will have an internal member with an instance of this struct. Within this example the struct contains the name and address of the person. Both elements are implemented as structs too. Furthermore for testing purposes we add some functionality to the person class. Within the ctor we create the person data and we add a method which writes the person name to the console.

struct Name
{
  std::string FirstName;
  std::string LastName;
};

struct Address
{
  std::string City;
  std::string Street;
};

struct PersonData
{
  Name mName;
  Address mAddress;
};

class Person
{
public:
  Person()
  {
    mPersonData = std::make_shared<PersonData>();
    mPersonData->mName.FirstName = "John";
    mPersonData->mName.LastName = "Doe";
    mPersonData->mAddress.City = "Springfield";
    mPersonData->mAddress.Street = "Main Street";
  }

  void ShowName()
  {
    std::cout << mPersonData->mName.FirstName << " " << mPersonData->mName.LastName << std::endl;
  }

private:
  std::shared_ptr<PersonData> mPersonData;
};

 

We will use this implementation for all following examples. To make the examples clearer I don’t want to show the whole source code every time. Therefore the next examples show an excerpt of the people class only, with additional methods we want to implement. Soo keep in mind that we always have the ctor which create the people data and the ShowName method which prints the name to the console.

 

Returning handle

The issue of this article is the question whether returning a handle to an internal member is possible without the risk to violate object oriented concepts. So we will start to an according method to the people class. Let’s say we want to get the name of the person. Therefore we return a handle to the according data member.

class Person
{
public:
  ...

  Name& GetName() { return mPersonData->mName; }

  ...
};

 

Within a client application we can use this method and get the name of the person. But this software design has a big disadvantage. The client will also get the possibility to change the object internal member, without knowing it or on purpose. The following code shows an according example. The client can set the name of the data object he received and if we call the ShowPerson method we will see that the class internal member was changed too.

int _tmain(int argc, _TCHAR* argv[])
{
  Person person = Person();
  person.ShowName();

  person.GetName().LastName = "Smith";
  person.ShowName();

  return 0;
}

 

By returning a handle to the internal member we make this member public. If we disclosure class internal details we will violate the object oriented concepts and create error-prone code. So we create a private member which is public on closer inspection. Unfortunately the compiler will not detect such unsafe implementations and therefore we do not get a compiler warning.

 

Const function

Constness is a strong and important C++ feature. By making a method const we will ensure that this method cannot change class internal members. That’s exactly what we want. So let’s make the method const and try again whether we can change the internal member.

class Person
{
public:
  ...

  Name& const GetName() { return mPersonData->mName; }

  ...
};

int _tmain(int argc, _TCHAR* argv[])
{
  Person person = Person();
  person.ShowName();

  person.GetName().LastName = "Smith";
  person.ShowName();

  return 0;
}

 

Unfortunately this has no effect. We can compile and execute the code and the internal member can still be changed. Is there something wrong with the const feature? No, everything is fine as we don’t change the person object. The person object stores the data by using a pointer to the person data object. By calling the GetName method we get this pointer and change the data. So the pointer itself will not be changed and the bitwise constness of the person object is guaranteed. As a result, from a compiler point of view this code is fine. From a user point of view we see that we have ignored the logical constness of the object but that’s something the compiler cannot detect.

 

Const handle

Another possibility may be to make the returned handle itself const. If we do so we have a read only handle and cannot change the internal member anymore. So let’s try this possibility.

class Person
{
public:
  ...

  const Name& const GetName() { return mPersonData->mName; }  

  ...
};

int _tmain(int argc, _TCHAR* argv[])
{
  Person person = Person();
  person.ShowName();

  person.GetName().LastName = "Smith";  // compilation error
  person.ShowName();

  return 0;
}

 

This change seems to work. We will now get a compiler error and cannot change the data object anymore. As conclusion this seems to be an easy solution for our issue. Just make the object handle read only and we cannot change it by mistake or on purpose.

But unfortunately there is another issue which can result in error-prone code and instable applications. We will now have read access to an object which is managed by the person class. And the person class thinks that this data object is one its private members. So the person class can change, invalidate and delete the data object and the underlying memory without respect of any existing client.

The following source code shows an according example. For test purposes I have created an Invalidate-method which resets the data object. This method is for test purposes only. Such a reset of the internal member can occur at any time as the person object can change its internal state whenever needed.

class Person
{
public:
  ...

  const Name& const GetName() { return mPersonData->mName; }  

  void Invalidate()
  {
    mPersonData.reset();
  }

  ...
};

int _tmain(int argc, _TCHAR* argv[])
{
  Person person = Person();
  person.ShowName();

  // get handle to the name object
  const Name* pName = &(person.GetName());

  // invalidate the internal member
  // such a reset can be done internally within the person object 
  // or it may be triggered by another thread or...
  person.Invalidate();

  // try to access the name handle data
  std::cout << pName->LastName;   // error during runtime

  return 0;
}

 

If we execute the test application we will get an error during runtime. We try to access the data member “pName->LastName;“  but the pointer to the data object is invalid and therefore we get an according exception.

Returning a handle is dangerous, even if it is a const one. This conclusion can be used as general guideline. Of course there may be exceptions to this rule but as a general guideline you shall keep in mind to not return handles.

If you have to make an exception to this rule, for example to create resource or speed optimized core components for your application or framework, you should minimize the chance for errors. In such a case you should use the according interface in a small and defined internal scope only. You should not return handles within an interface which is used by external clients. And of course you have to add according source code documentation explaining how to use the code and the risk in case this code is not used as intended.

 

Summary

Avoid returning handles to object internals.  To keep internal members private increases encapsulation and helps to write robust and clean code.

Advertisements
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s