Object orientation in C# and C++

Within this article I want to think about the possible implementations in C# and C++ to fulfill the base concept of object orientation. That’s a difficult task because if you start to think about object orientation and ask yourself what is the “base concept” you may have several ideas.

I started to think about this topic relating to an analysis about the root causes of errors in past projects and issues in legacy code. The root cause of most errors is a violation of core software development concept. I think the most important concept is the programming paradigm you are using: object orientation, functional programming or procedural programming – to count the most important ones. The projects I have worked so far are mostly based on the object-oriented paradigm. Therefore, most issues were a result of violating object oriented concept. Moreover, I have seen one core concept which will be ignored very often, even by experienced software developers. Therefore, I want to work out the core concepts and think about possible implementation in C# and C++.

 

The core concept of object oriented programming

The object-oriented paradigm is based on few concepts like: objects, classes, encapsulation, interfaces, inheritance, messaging, composition, delegation, polymorphism and many more. But I think most of these concepts are optional features only. Of course, they are very important, but they are not necessary for object orientation. For example, think about “inheritance”. If you ask software developers about the concepts of object orientation you will hear “inheritance” as first, second or latest as third concept. But in my opinion, it is an optional feature only. You can implement object oriented without using inheritance and you can develop an object-oriented language which even does not support inheritance.

If all these things are optional features only, what will remain? In my opinion, the core concept of object orientation is: “Objects and their coupling, where an object is memory management hidden by an interface”.

Let us think about this approach. We have two core components: objects and (their) coupling. The easy part is “coupling” because if we have several objects then of course, they should work together somehow. The more difficult part is the “object”. That’s a well-known term in software development, but unfortunately there are a lot of different opinions about the question: What is an object?

I like to explain an object as: “memory management hidden by an interface”. That’s a very technical description but it contains the core concepts of on object and is independent of the used programming language.

At this point I want to stop and may ask you to find you own definition for object orientation. The above definition reflects my point of view only. You will find another one and of course their existing several definitions which are all fine.

 

Coupling of objects

As mentioned above “coupling” is the easy one of the two concepts. The concept itself is easy to understand and the implementation is well supported in most languages. And the violation of this concept does not result in errors in most cases. But it results in code which is difficult to understand, to maintain and to extend. So, a violation of this concept will increase the development costs.

So, what kind of coupling do we want to have between our objects? A very loose one! In a perfect world, objects will do their work independent of other objects. If an object shall do anything it gets a trigger and the needed information, executes its task and will inform the object(s) which are waiting to get the result. That will allow small, independent, reusable objects. Depending on the use case the needed object will be defined and loose coupled by establishing communication channels and then they will execute the task.

What will happen if we do not respect this concept an implement a system with a lot of dependencies between objects? It will lead to the same issues as we know from classical procedural programming using jump instructions like “goto”. The result is a software system with a complex control flow, better known as “spaghetti code”. As we banished “goto” statements in modern software systems, we also should try to banish object coupling.

There are several concepts to implement such system of loose coupled objects, for example: Event based programming. Within this article I don’t want to show possible implementation of such an event bases system. That’s because most programming languages offer very easy to use messaging or event mechanism. You will find a lot of online resources about possible patterns to implement event based systems. Therefore, I want to focus on the difficult part, the “object”.

 

Objects

As described before, I want to define an object as “memory management hidden by an interface”. The interface contains the public accessible methods and properties of the object. The implementation details itself are internal and hidden. So, the object is a black box and a client does know the public interface only. All internal details are hidden and the object has to have the responsibility to manage its resources. In most cases resource management is memory management. Of course, there are other resources like a network connection. So my definition for an object is not imperfect and should be changed to “resource management hidden by an interface”. But I prefer the term “memory management” as most of the implementation errors are associated with wrong memory management. The generic term “resource management” sometimes leads to misunderstanding of the real issue. For example, if your object contains an internal data member it is not sufficient to hide this resource itself by making it private. Moreover, you have to hide the memory management used by your private member.

Again, we can think about the consequences in case we don’t follow this concept. If we have object with public memory management it is the same as implementing with public variables only. Everyone can read and write these public variables at any time. This will result in a complex system of data flows. And compared with the issue of complex control flows, which we have seen in scenarios with a lot of dependencies between the objects, the situation gets even worse. Complex control flows are very difficult to understand and will normally result in much higher development costs. But complex data flows are a mess and will result in errors.

 

Private memory management

An object is a black box for the client. The memory management must be hidden and therefore not visible within the public interface, whether direct nether indirect. So, we have to respect this concept of hidden memory management for data which is used as input for the object and data which is used as output. Input data, e.g. ctor parameters, setter or function parameters, should allow accessing the data only and store it within the memory managed by the object. Output data, e.g. getter or function results, shall contain data only and shall not allow accessing the memory addresses where the data is stored. In summary, the public interface of an object defines the data flow to and from the object but it explicitly hides memory management details.

 

Base concept in C#

C# has an own memory management system and hides the internal pointer management. This concept of implicit referencing allows an easy and efficient memory handling. But as the developer will use pointers implicitly in form of reference types he may forget about the fact that variables of such types are pointers to memory which is managed by someone. Because of this implicit referencing you will find a lot of C# source code where the core concept of object orientation, the concept of hidden memory management, is violated.

The following source code shows an example implementation. We have an object, the “employee” class, with a public interface which is used by a client. And we have a second object, the “person” class, which encapsulates the management of a person. This person class is used by the employee object.

class Person
{
  public Person(string name, int age)
  {
    mName = name;
    mAge = age;
  }

  public string mName;
  public int mAge;
}

class Employee
{
  public Employee(Person person, string phoneNumber)
  {
    mPerson = person;
    mPhoneNumber = phoneNumber;
  }

  public Person Person
  {
    get { return mPerson; }
  }

  public void PrintName()
  {
    Console.WriteLine(mPerson.mName + ", " + mPerson.mAge.ToString() + ", " + mPhoneNumber);
  }

  private Person mPerson;
  private string mPhoneNumber;
}

class Program
{
  static void Main(string[] args)
  {
    Person person = new Person("John Doe", 32);
    string phoneNumber = "555 123456";
    Employee employee = new Employee(person, phoneNumber);

    employee.PrintName();
  }
}

 

As mentioned before we want to think about the memory management guideline only as this is the most difficult and critical aspect of the object-oriented concept. What do you think about this implementation? Is it according the object-oriented concept?

Unfortunately, it is not. And I say “Unfortunately” because I think you can see this kind of implementation very often within C# applications. But what’s the issue with this code? I can demonstrate this with a little modification of the client code.

class Program
{
  static void Main(string[] args)
  {
    Person person = new Person("John Doe", 32);
    string phoneNumber = "555 123456";
    Employee employee = new Employee(person, phoneNumber);

    employee.PrintName();

    person.mName = "Foo Bar";
    person.mAge = 41;
    phoneNumber = "555 111111";

    employee.PrintName();

    person = employee.Person;
    person.mName = "Jane Doe";
    person.mAge = 28;
    phoneNumber = "555 999999";

    employee.PrintName();
  }
}

 

Ups, the client can manipulate the object internal data. And he will not do this on purpose. The code shows typical use cases were the client works with variables he manages and he does not know or even expect that he changes something inside the employee object. Such code will result in critical errors.

Is this a fault of the C# language? No, it isn’t. C# will protect the pointer but not it’s content. The pointer itself is immutable unless you specify a “ref” modifier. So, you cannot change the pointer but of course you can access and change the data of the pointer.

The error occurred as the object shows the memory management within its public interface. The ctor parameter is a reference to memory and the getter property is a reference to memory too. So, the memory management is not hidden, instead it is totally transparent. This violates the core concept of object orientation and results in such critical errors.

Following I want to show two possible implementations for this use case. Of course, these two implementations are not the only ones, but they show two common concepts.

 

Pass copies and return copies in C#

The object and the client want and must work with their own independent data. Therefore, unless whether they use different data type or whether they even if they use the same data structures – in this example the person class – the data must be available as often as they are used. Within this example the client creates an employee object and passes a person object. So, it manages one person instance. The employee object itself contains a hidden internal data member which is also a person class. And later, the client or maybe another client will get data from the employee class. This data contains a person class too. But of course, these three instances of the person class are managed in an own context. So, they must be independent of each other which means they must contain the same data but an own memory management. And that’s easy to implement. We just should create copies. The following source code shows a possible implementation.

class Person
{
  public Person(string name, int age)
  {
    mName = name;
    mAge = age;
  }

  public Person(Person person)
  {
    mName = person.mName;
    mAge = person.mAge;
  }

  public string mName;
  public int mAge;
}

class Employee
{
  public Employee(Person person, string phoneNumber)
  {
    mPerson = new Person(person);
    mPhoneNumber = phoneNumber;
  }

  public Person Person
  {
    get { return new Person(mPerson); }
  }

  public void PrintName()
  {
    Console.WriteLine(mPerson.mName + ", " + mPerson.mAge.ToString() + ", " + mPhoneNumber);
  }

  private Person mPerson;
  private string mPhoneNumber;
}

 

This implementation has on major disadvantage: copying of object may be slow, especially if you have huge objects. The next example will show another implementation which will not copy objects but make them immutable instead.

 

Use immutable objects in C#

Depending on the use case it may not be necessary to create a copy. For example, if you have data which is nearly never changed, like settings parameters, you may copy this data very often but change it infrequent. That means you want to read the data often but write access is needed rarely. Such use cases can be implemented by using immutable objects. This will allow you to pass a reference to the data. But this reference will be read only. So, you can use it within your public interface. In this case you will hide the memory management even if you give information about memory addresses. That’s because the client cannot specify the memory address within input variables – internally you can use the address or create your own memory object – and the client cannot write something to memory addresses of function results. As the function results are immutable the client should create its own objects if he wants to make some changes.

The following source code shows a possible implementation.

class Person
{
  public Person(string name, int age)
  {
    mName = name;
    mAge = age;
  }

  public Person(Person person)
  {
    mName = person.mName;
    mAge = person.mAge;
  }

  public readonly string mName;
  public readonly int mAge;
}

class Employee
{
  public Employee(Person person, string phoneNumber)
  {
    mPerson = person;
    mPhoneNumber = phoneNumber;
  }

  public Person Person
  {
    get { return mPerson; }
  }

  public void PrintName()
  {
    Console.WriteLine(mPerson.mName + ", " + mPerson.mAge.ToString() + ", " + mPhoneNumber);
  }

  private readonly Person mPerson;
  private readonly string mPhoneNumber;
}

 

If you try to compile the client code you will now get compiler errors and the client must now change its implementation and create copies of the immutable objects.

class Program
{
  static void Main(string[] args)
  {
    Person person = new Person("John Doe", 32);
    string phoneNumber = "555 123456";
    Employee employee = new Employee(person, phoneNumber);

    employee.PrintName();

    person.mName = "Foo Bar";	// --> error
    person.mAge = 41;		// --> error
    phoneNumber = "555 111111";

    employee.PrintName();

    person = employee.Person;	
    person.mName = "Jane Doe";	// --> error
    person.mAge = 28;		// --> error
    phoneNumber = "555 999999";

    employee.PrintName();
  }
}

 

Base concept in C++

At next we want to change from C# to C++. To compare the possibilities within both languages we use the same example application.

But at first, we want to think about the base concept regarding memory management in objects. In C++ the developer uses explicit pointers. He allocates and releases the memory explicitly.

In C++ parameters and results are transferred as copy by default. Therefore, the solution we found in C# – pass and return copies – is the default behavior in C++. This default behavior reduces errors but it is slow for larger objects. So, we will often implement interfaces with pointers as parameters.  In C++ we have same base concepts which allow robust interfaces, even we use pointers. These concepts are: “pass by reference to const”, “const correctness” and “avoid returning handles to object internals”. If you not familiar with these concepts please look for according articles (for example within this blog) as I will use the concepts within the example application.

 

Pass copies and return copies in C++

The following code shows the example application written in C++. It also contains the client which is working with the parameters passed and returned to the employee object to manipulate the object internals.

class Person
{
public:
  Person() {}

  Person(std::string name, int age) : mName(name), mAge(age)
  {
  }

  std::string mName;
  int mAge;
};

class Employee
{
public:
  Employee() {}

  Employee(Person person, std::string phoneNumber) : mPerson(person), mPhoneNumber(phoneNumber)
  {   
  }

  Person GetPerson() { return mPerson; }

  void PrintName()
  {
    std::cout << mPerson.mName << ", " << mPerson.mAge << ", " << mPhoneNumber << std::endl;
  }

private:
  Person mPerson;
  std::string mPhoneNumber;
};

int _tmain(int argc, _TCHAR* argv[])
{
  Person person = Person("John Doe", 32);
  std::string phoneNumber = "555 123456";
  Employee employee = Employee(person, phoneNumber);

  employee.PrintName();

  person.mName = "Foo Bar";
  person.mAge = 41;
  phoneNumber = "555 111111";

  employee.PrintName();

  person = employee.GetPerson();
  person.mName = "Jane Doe";
  person.mAge = 28;
  phoneNumber = "555 999999";

  employee.PrintName();

  return 0;
}

 

As C++ passes and returns copies of objects by default, the C++ example works fine.

 

Use pointers in C++

At next we want to change our example to use pointers, e.g. because the objects may be huge and the copies therefore expensive. The following example will use a smart pointer to manage the person data.

class Person
{
public:
  Person() {}

  Person(std::string name, int age) : mName(name), mAge(age)
  {
  }

  std::string mName;
  int mAge;
};

class Employee
{
public:
  Employee() {}
  
  Employee(std::shared_ptr<Person> person, std::string phoneNumber) : mPhoneNumber(phoneNumber)
  {
    mPerson = std::move(person);
  }
  
  std::shared_ptr<Person> GetPerson() { return mPerson; }

  void PrintName()
  {
    std::cout << mPerson->mName << ", " << mPerson->mAge << ", " << mPhoneNumber << std::endl;
  }

private:
  std::shared_ptr<Person> mPerson;
  std::string mPhoneNumber;
};

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<Person> person = std::make_shared<Person>("John Doe", 32);  
  std::string phoneNumber = "555 123456";
  Employee employee = Employee(person, phoneNumber);

  employee.PrintName();

  person->mName = "Foo Bar";
  person->mAge = 41;
  phoneNumber = "555 111111";

  employee.PrintName();

  person = employee.GetPerson();
  person->mName = "Jane Doe";
  person->mAge = 28;
  
  employee.PrintName();

  return 0;
}

 

If we execute the application we will now cause the same issue as in C#. The client manipulates the employee object internal data without knowing it. So, we will violate the base concept of a hidden memory management.

But wait a moment, in C++ we have the nice feature if “const correctness”. Let’s use this feature to pass and return const parameters and results. The following source code contains the previous example, extended by const-expression.

class Person
{
public:
  Person() {}

  Person(const std::string name, const int age) : mName(name), mAge(age)
  {
  }

  std::string mName;
  int mAge;
};

class Employee
{
public:
  Employee() {}

  Employee(const std::shared_ptr<Person> person, const std::string phoneNumber) : mPhoneNumber(phoneNumber)
  {
    mPerson = std::move(person);
  }

  const std::shared_ptr<Person> GetPerson() const { return mPerson; }

  void PrintName() const
  {
    std::cout << mPerson->mName << ", " << mPerson->mAge << ", " << mPhoneNumber << std::endl;
  }

private:
  std::shared_ptr<Person> mPerson;
  std::string mPhoneNumber;
};

 

Unfortunately, this changes nearly nothing. “Const correctness” is an important feature and you should design every interface with respect to this concept. But in our case, it does not solve the memory management issue. And that’s because C++ ensures a bitwise constness only. We did not change the pointer itself, so the bitwise representation of our object instance is unchanged. But we changed the content of the memory the pointer is referencing. But this logical constness cannot be checked by a compiler because it is use case specific. Therefore “Const correctness” will help us because the compiler can check the bitwise constness but as developers we should implement a logical constness ourselves because it is use case dependent.

 

Initialize with copy and return read only pointers in C++

As described above the employee object wants to hold own data and therefore had should create an own instance of the needed data and initialize it with the input parameters of the ctor. As the client does not know anything about the internal data management of the object it should pass the data as reference to const and the called object ctor itself is responsible to created data copies if needed. Getter function which contain information about internal data can create data copies too. This depends on the according use case. If copies are expensive and should be avoided, it is possible to return a const reference to some internal data. In this case the client has read only access. If the client wants to change some data of the returned object it had to create an own copy of the data.

The following source code shows a possible implementation.

class Person
{
public:
  Person() {}

  Person(std::string name, int age) : mName(name), mAge(age)
  {
  }

  std::string mName;
  int mAge;
};

class Employee
{
public:
  Employee() {}
  
  Employee(const Person& person, std::string phoneNumber) : mPhoneNumber(phoneNumber)
  {
    mPerson = std::make_shared<Person>(person);    
  }
  
  std::shared_ptr<const Person> GetPerson() { return mPerson; }

  void PrintName()
  {
    std::cout << mPerson->mName << ", " << mPerson->mAge << ", " << mPhoneNumber << std::endl;
  }

private:
  std::shared_ptr<Person> mPerson;
  std::string mPhoneNumber;
};

 

The according client code will now generate compiler errors in case the client tries to change the value of the getter directly.

int _tmain(int argc, _TCHAR* argv[])
{
  Person person = Person("John Doe", 32);
  std::string phoneNumber = "555 123456";
  Employee employee = Employee(person, phoneNumber);  

  employee.PrintName();

  person.mName = "Foo Bar";
  person.mAge = 41;
  phoneNumber = "555 111111";

  employee.PrintName();

  std::shared_ptr<const Person> pPerson = employee.GetPerson();
  pPerson->mName = "Jane Doe";	// error
  pPerson->mAge = 28;		// error

  employee.PrintName();

  return 0;
}

 

 

Use immutable objects in C++

Like in C# you can make the data objects immutable. The following source code shows the adapted example application.

class Person
{
public:
  Person(std::string name, int age) : mName(name), mAge(age)
  {
  }

  const std::string mName;
  const int mAge;
};

class Employee
{
public:
  Employee() {}
  
  Employee(std::shared_ptr<Person> person, std::string phoneNumber) : mPhoneNumber(phoneNumber)
  {
    mPerson = std::move(person);
  }
  
  std::shared_ptr<Person> GetPerson() { return mPerson; }

  void PrintName()
  {
    std::cout << mPerson->mName << ", " << mPerson->mAge << ", " << mPhoneNumber << std::endl;
  }

private:
  std::shared_ptr<Person> mPerson;
  const std::string mPhoneNumber;
};

 

If the client now tries to change the input or output parameter, a compiler error will be shown. The following source code shows the client application and the four lines of codes with compiler errors.

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<Person> person = std::make_shared<Person>("John Doe", 32);  
  std::string phoneNumber = "555 123456";
  Employee employee = Employee(person, phoneNumber);

  employee.PrintName();

  person->mName = "Foo Bar";	// error
  person->mAge = 41;		// error
  phoneNumber = "555 111111";

  employee.PrintName();

  person = employee.GetPerson();
  person->mName = "Jane Doe";	// error
  person->mAge = 28;		// error
  
  employee.PrintName();

  return 0;
}

 

Use C# or C++?

At this point, I would deliberately avoid comparing the two languages. Both have a nice support for object-oriented development. Some of the concepts are equal but there are some differences too. But I think the differences regarding the object-oriented concepts are too small and therefore should never influence your choice for one of the languages. If you start a new project, the selection of the language should be driven by the environmental and quality needs and not by technical preferences. For example, you should not prefer C++ over C# because the developer likes multiple inheritance. And even the programming paradigm should be chosen with respect to the actual project. The object-oriented paradigm may not be the best choice for your project.

 

Summary

To get a high execution performance, pointers are heavily used as well in C# as in C++. In C#, they are normally used implicit and in C++ explicit. Both programming languages contain language features and concepts for object-oriented implementation. But of course, as the languages are very flexible and not limited to single programming paradigms, they cannot prevent the developer doing conceptual errors. Within this article I focused on one important concept of object-oriented development – the private memory management of objects – and introduced some possible implementations according to this concept. As this is a complex topic the article should help you to open your mind for this subject, show typical implementation errors and give you a short introduction into possible solutions.

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