Friday, July 25, 2014

[Programming] #5 Passing by value and reference, with a pinch of const

This is going to just be a alternate perspective of the good old "pass by values vs pass by reference" programming topic.

The motivation of writing this stems from me currently in a working environment with programmers coming from so many different backgrounds and when they land into a project in C++, they tend to get lost about this only because they are not used to it (these are good programmers who actually know their shit).

The other motivation is so that when I encounter programmers who do not completely understand this, I can just copy paste this to them and hopefully they understand.

Needless to say, I would like to thank Java, javascript, PHP and all those other languages for blurring the lines between passing by reference and passing by value. THANKS =/

Anyway. This is written with cocos2d-x classes.

There are 2 ways of how you would pass an object in C++ (I will used a more extreme example like CCArray):

void foo( CCArray array ) //pass by value

and
void boo( CCArray& rArray ) // pass by reference


In the first line, the object is passed by value. This means that every time you call the function foo(), it will literally take your original CCArray, create a copy of CCArray and copy *each and every* of the original's content into the copy before the function starts. Some humans usually call this 'piracy', and while the idea of piracy is 'nice' and all, it takes up more space and it takes time to copy.

In the worse case common scenario, you might write code like this:

class SomeClass {
public:
  foo(CCArray array);
private:
  m_array;
}

void SomeClass::foo( CCArray array ) {
  m_array = array; // wut
}

// somewhere else
SomeClass myClass;
CCArray array(); // pretend that there is 1000 objects in pArray
myClass.foo(pArray);

In this case, when you call 'myClass.foo(pArray)', it will first copy pArray into a separate CCArray before the start of the function.

Within the function, thanks to the awesome assignment operator in m_array = array, it will attempt to copy the copied CCArray 'array' into a new copy 'm_array'! (Double piracy!)

This is obviously not very good. The second copying is actually fine because we want our own copy of the CCArray, but the first copying process feels redundant. Why should we copy twice for one copy?

The alternative is to pass by reference:

void SomeClass::foo( CCArray& rArray ) {
  m_array = rArray ;
}

SomeClass myClass;
CCArray pArray();
myClass.foo(pArray); // pretend that there is 1000 objects in pArray

This passes the reference; of pArray into the function, that is, the original pArray into the function. Unfortunately, because it is the original copy, it means that the function foo() can do maliciously evil despicable things like:

void SomeClass::foo( CCArray& rArray ) {
  m_array = array;

  //evil malicious things by adding 1000 more rubbish!
  for ( int i = 0 ; i < 1000; ++i ) {
    m_array->addObject(CCObject::create())
  }
}

SomeClass myClass;
CCArray array();    // pretend that there is 1000 objects in pArray

// now there are 2000 objects instead of 1000 objects in pArray and you are officially heartbroken because you might have wasted 10 hours debugging why.
myClass.foo(pArray);

This actually happens in Java often because Java people are trust each other.
However, C++ programmers do not trust each other so we have a wonderful keyword called const.

With this const keyword, we promise to the users that we will never modify their object:

void SomeClass::foo( const CCArray& array ) {
  m_array = array;

  //COMPILE ERROR!
  for ( int i = 0 ; i < 1000; ++i ) {
    m_array->addObject(CCObject::create());
  }
}

This is why you see argument declarations like "const std::string& str" being used.

But wait, what about pointers?

Pointers work exactly like a value. In other words, there is really no difference between passing by value and passing by pointer IF you treat pointers like an object on it's own (i.e, do not think about its relation to the object so much).

Pointers are simply 4 byte objects that store addresses.

Consider:
void SomeClass::foo( const CCArray *pArray ) {
  // Assume m_array is now "const CCArray * m_array"
  m_array = pArray ;
}
SomeClass myClass;
CCArray array();
myClass.foo(&array);

This simply means that the address of 'array' is copied into 'pArray' and then copied over to 'm_array'. All 3 variables are different variables (they are essentially different) but they hold the same value. Much like if you set i = j = k = 0, all i, j and k are different variables but they all hold 0. It's pretty much the same concept.

Again: Pointers store addresses.

This is not that much different:
void SomeClass::foo( const CCArray *pArray ) {
  // Assume m_array is now "const CCArray * m_array"
  m_array = pArray ;
}
SomeClass myClass;
CCArray * array = CCArray::create(); // now we try to pass by pointer
myClass.foo(array);

Okay read this slowly:

This means that the value held by 'array' is copied into 'pArray' and then copied into 'm_array'. The difference is that the former assigns the address of 'array' into 'pArray', while the latter copies the value held by 'array' into 'pArray'. Both methods have more or less the same results, although what happens is sublimely different.

While there is no deep copying involved in both cases, we must remember that this only means that 'm_array' cannot change the value of the object it is pointing to, but 'array' can. That means that if the object that 'array' is pointing to is modified, the object that 'm_array' is pointing to will be modified too.

After all, even though the pointers are different, they store the same addresses and thus they are pointing to the same object.

No comments:

Post a Comment