[Scummvm-devel] Being memory & speed efficient when dealing with C++ objects

Max Horn max at quendi.de
Sat Jun 3 18:21:13 CEST 2006


Hi there,

the following might be old news to some of you, but maybe a few  
people out there can learn something from this, so I thought I post  
it here anyway.

When dealing with objects in C++, one has to be careful if one wants  
to be speed and memory efficient. C++ has this habit of copying  
objects in places were you might not expect it. Especially if you are  
from a Java / Python / other GC collected background.

Consider this statement:
   String myStr = someOtherString;
This is in fact equivalent to
   String myStr;
   myStr = someOtherString;
In other words, it will first create the string object myStr (on the  
stack). *Then* it will invoke the assignment operator, overriding the  
default value of myStr. Not a big deal here: It means we allocate a  
refcount pointer, only to delete it again a few cycles later. But  
imagine we did this a couple hundred thousand times, and it adds up.  
Or imagine we had used SomeBigObject instead of String, which has a  
very expensive default constructor ... Not nice.

So the proper efficient way to write the above statement in C++ would  
be:
   String myStr(someOtherString);

This calls the copy constructor of class String directly, thus  
avoiding the default initialization of the string class. Before you  
shout now "boo, C++ is stupid, why doesn't it just 'Do What I  
Mean' (TM) and optimize that useless default constructor call  
away???". Simple: Because in many cases it can't, due to side  
effects. Imagine we had a printf() in the default constructor. If the  
compiler would simply optimize away the call to the default  
constructor, it would also remove that printf call. Which would  
result in changed program behavior. Which clearly is something an  
optimizer is not allowed to do!


Another *very* typical pattern were people not very deeply intimate  
with C++ code inefficiently are method parameters. Consider the  
following three methods:

   void MyClass::myClearVar1(String var) {
     myVarTable[var] = 0;
   }

   void MyClass::myClearVar2(const String var) {
     myVarTable[var] = 0;
   }

   void MyClass::myClearVar3(const String &var) {
     myVarTable[var] = 0;
   }


Look pretty similar, don't they? But they are quite different. If you do
   myObj.myClearVar1(someVarName);
then this will actually create a temporary String object 'var' which  
contains a *copy* of someVarName. The same holds for
   myObj.myClearVar2(someVarName);
the only difference being that the copy is now declared const. Again,  
the compiler can't just optimize this away, this time due to possible  
side effects in the copy constructor.

The efficient way is to use variant 3, for
   myObj.myClearVar3(someVarName);
will pass someVarName *by reference* (think of it like a pointer in  
disguise). No copy is made.


Luckily, with our String class, the first two methods aren't that  
bad, since our String class uses ref counting, so making copies is  
cheap, as long as you don't modify the copied content (which is  
impossible in myClearVar2, of course, since it is declared const).  
Still, some memory on the stack will be used, and there is simply no  
excuse not do use variant 3.


I hope this shed some light on this dark mystery. And please feel  
free to ask should something be unclear!


Cheers,
Max




More information about the Scummvm-devel mailing list