Sunday 7 November 2010

Has-a relationship: reference, pointer or value? ( 1/2 )



You are going to implement an Has-a relationship between two classes. Which is the best way to model this relation: by reference, by pointer or by value?


By Value
In general this is the preferable way to implement an Has-a relationship, the main advantage is to have to complete ownership of the object. Let's have a look at this small chunk of code:
  1. class Component
  2. {
  3.     public:
  4.         /* some stuff */
  5.     private:
  6.         /* some other stuff */
  7. };
  8. class Owner
  9. {
  10.     public:
  11.         Owner(const Component& component)
  12.             :component_(component) {}
  13.     private:
  14.         Component component_;
  15. };
So far so good, every time we create a relationship between two objects we bond the behaviour of one object to the interface of the other one. I will use this simple example to show how the Owner class will change based upon the Component interface.
The constructor of Owner takes a const reference of a component object. The keyword  const is important because we don't and we shouldn't change the object that we are going to copy and store on our class (the keyword explicit is to avoid undesired automatic conversion, have a look at Strongly Type post for more information).

The first drawback of storing the new component by value is performance, we are copying a value, so if Component object is big we spend a lot of time copying it. Please note that the object can be indefinitely big. For example a std::vector can be very-very-very big.
  1. class Component
  2. {
  3.     private:
  4.         /* some stuff */
  5.         Component(const Component& other);
  6.         Component& operator=(const Component& other);
  7.     public:
  8.         /* some other stuff */
  9. };
The author of Component is a good developer and he knows that copying a very-very-very big object is bad, so he decided to disallow the copy semantic (as above).

Now we have the first trouble, the Owner class will not compile! So, the sad moral of the story is: we cannot implement has-a relationship by value if the component object has not copy semantic.


This is not the worst case scenario, let's take as example this Component interface:
  1. class Component
  2. {
  3.     public:
  4.         /* some stuff */
  5.         Component(Component& other);
  6.         Component& operator=(Component& other);
  7.     private:
  8.         /* some other stuff */
  9. };
This time the copy semantic is allowed but the keywords const has been removed. Why? I don't know the people are different and the developers too. Removing the const keyword can give you a very small advantage in performance (do you really need it?) but it can spoil the day of another developer. How? This example explains it:
  1. Component comp;
  2. /* some calculation */
  3. /* comp is very important! My whole program
  4.    is based on it! */
  5. Owner o(comp);
  6.    
  7. /* Arggggg.... Owner can change it! */
Changing an object that you take as an input parameter is like scratching the car of your friend. You shouldn't do that.
So: we can but we shouldn't implement an has-a relationship by value if the component object has non-const copy semantic.


Actually, we haven't finished yet with our analysis. There is another pitfall to avoid. It is a well known feature of C++, if you don't provide Constructor, Destructor, Copy-Constructor and Assignment Operator the compiler provide a version for you. You may think cool, less work. Actually there is huge drawback. The compiler is smart, but it is not a mind reader. Even the smartest compiler cannot reproduce the copy semantic that you think! You need to code it, otherwise the compiler will do the best and it can creating a bitwise copy. Bitwise copy is an exact copy of an existing object bit by bit. In order to show all the possible outcomes I have prepared a small program. Even though the program is completely meaningless the result output of it is exactly what we want:
  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. class Component
  5. {
  6.     public:
  7.         /* some stuff */
  8.         Component(int numberOfValue,string myString) :
  9.             i_(new int[numberOfValue]),
  10.             numberOfValue_(numberOfValue),
  11.             myString_(myString)
  12.         {
  13.             changeValue(10);
  14.         }
  15.            
  16.         ~Component() { delete[] i_; }    
  17.        
  18.         int getNumberOfValue() const { return numberOfValue_; }
  19.        
  20.         string getMyString() const { return myString_; }
  21.        
  22.         void printArray() const
  23.         {
  24.             for (int j=0; j < numberOfValue_; j++)
  25.                 cout << i_[ j ] << " " ;
  26.             cout << " Data stored by pointer" << endl;           
  27.         }
  28.        
  29.         void modify(int newNumberOfValue,string anotherString)
  30.         {
  31.             delete[] i_;
  32.             i_ = new int[newNumberOfValue];
  33.             numberOfValue_ = newNumberOfValue;     
  34.             myString_ = anotherString;
  35.             changeValue(100);
  36.         }
  37.     private:
  38.         void changeValue(int leverage)
  39.         {
  40.             for (int j=0; j < numberOfValue_; j++)
  41.                 i_[ j ] = leverage*j;          
  42.         }
  43.         /* some other stuff */
  44.         int* i_;
  45.         int numberOfValue_;
  46.         string myString_;
  47. };
  48. class Owner
  49. {
  50.     public:
  51.         Owner(Component& component)
  52.             :component_(component)
  53.     {
  54.         component_.modify(component_.getNumberOfValue(), "Bar");   
  55.     }
  56.    
  57.     private:
  58.         Component component_;
  59. };
  60. int main (int argc, char *argv[])
  61. {
  62.     //numberOfValue equal to 10
  63.     Component comp(10,"Foo");
  64.     cout << comp.getNumberOfValue() << " Built-in type "  << endl;
  65.     cout << comp.getMyString() << " Object with deep-copy semantic" << endl;
  66.     comp.printArray();
  67.        
  68.     //Make a copy of Component!
  69.     //Using the compiler automatic
  70.     //generated copy consturctor
  71.     Owner o(comp);
  72.     cout << comp.getNumberOfValue() << " Built-in type "  << endl;
  73.     cout << comp.getMyString() << " Object with deep-copy semantic" << endl;
  74.     comp.printArray();
  75.     return 0;
  76. }
The output is:
10 Built-in type
Foo Object with deep-copy semantic
0 10 20 30 40 50 60 70 80 90  Data stored by pointer
10 Built-in type
Foo Object with deep-copy semantic
0 100 200 300 400 500 600 700 800 900  Data stored by pointer



As you can see the Built-in type is copy correctly and also the string object. The array is doing something odd. The value of the array has been modified after the creation of Owner object. The reason is that the compiler makes a shallow copy of the pointer, it copies just the pointer and not all the data pointed by the pointer. So you will end up with two entities sharing the same data. Sometimes is what you want but very often is not.



So: we shouldn't implement an has-a relationship by value if the component object hasn't a deep copy semantic. If we really want to do that we need to carefully document our choice because it is not the behaviour that the client of our class will expect.

No comments:

Post a Comment