In the first part of this blog post (Is pre-increment really faster than post increment? Part 1) I showed that it really doesn’t seem to matter if you use post or pre increment with simple integer types.
I then promised an example of where the choice of pre or post increment DOES matter, and here it is.
The long and the short of it is this…
- When you pre increment a variable, you are changing the value of a variable, and the new value will be used for whatever code the pre increment is part of.
- When you post increment a variable, you are changing the value of a variable, but the OLD value is used for whatever code the post increment is part of.
To make post increment work that way, it essentially needs to make a copy of the variable before the increment and return the copy for use by the code of which the post increment is a part of.
A pre increment has no such need, it modifies the value and everything uses the same variable. There is no copy needed.
The compiler / optimizer apparently does a good job of figuring out when it does or does not need to make a copy of integral types, but it doesn’t do as well with complex objects. Here’s some sample code to demonstrate this and the output that it generates in both debug and release.
Test Code
#include #include #include //============================================================= class CScopeMessage { public: CScopeMessage(const char *label) { PrintIndent(); printf("%srn", label); s_indentLevel++; } CScopeMessage(const char *label, int objectId) { PrintIndent(); printf("%s obj %irn", label, objectId); s_indentLevel++; } CScopeMessage(const char *label, int objectId, int copyObjectId) { PrintIndent(); printf("%s (obj %i) to obj %irn", label, objectId, copyObjectId); s_indentLevel++; } ~CScopeMessage() { s_indentLevel--; } static void StartNewTest() { s_indentLevel = 0; s_lineNumber = 0; printf("rn"); } private: void PrintIndent() { s_lineNumber++; printf("%2i: ", s_lineNumber); for(int index = 0; index < s_indentLevel; ++index) printf(" "); } private: static int s_indentLevel; static int s_lineNumber; }; int CScopeMessage::s_indentLevel = 0; int CScopeMessage::s_lineNumber = 0; //============================================================= class CTestClass { public: CTestClass() { m_objectID = s_objectID; s_objectID++; // this is just noise in the test, but feel free to // comment out if you want to see for yourself //CScopeMessage msg("Constructing", m_objectID); m_value = new char[4]; strcpy(m_value, "one"); } CTestClass(const CTestClass &other) { m_objectID = s_objectID; s_objectID++; CScopeMessage msg("Copy Constructing", other.m_objectID, m_objectID); m_value = new char[strlen(other.m_value) + 1]; strcpy(m_value, other.m_value); } ~CTestClass() { CScopeMessage msg("Destroying", m_objectID); delete[] m_value; } // preincrement CTestClass &operator++() { CScopeMessage msg("Pre Increment", m_objectID); DoIncrement(); return *this; } // postincrement CTestClass operator++(int) { CScopeMessage msg("Post Increment", m_objectID); CTestClass result(*this); DoIncrement(); return result; } void DoIncrement() { CScopeMessage msg("Doing Increment", m_objectID); } private: char *m_value; int m_objectID; static int s_objectID; }; int CTestClass::s_objectID = 0; //============================================================= int main (int argc, char **argv) { CTestClass test; { CScopeMessage msg("--Post Increment--"); test++; } CScopeMessage::StartNewTest(); { CScopeMessage msg("--Post Increment Assign--"); CTestClass testB = test++; } CScopeMessage::StartNewTest(); { CScopeMessage msg("--Pre Increment--"); ++test; } CScopeMessage::StartNewTest(); { CScopeMessage msg("--Pre Increment Assign--"); CTestClass testB = ++test; } system("pause"); return 0; }
Debug
Here’s the debug output:
You can see that in the post increment operator, it calls the copy constructor not once but twice! The first copy constructor is called to create the “result” object, and the second copy constructor is called to return it by value to the caller.
CTestClass operator++(int) { CScopeMessage msg("Post Increment", m_objectID); CTestClass result(*this); DoIncrement(); return result; }
Note that it can’t return the copy by reference because it’s a local variable. C++11’s “std::move” and xvalue type functionality is there to help with this stuff, but if you can’t use that tech yet, it isn’t much help hehe.
Interestingly, we can see that 2 copy constructors get called whether or not we assign the value returned or not.
On the pre-increment side, you can see that it only does a copy construction call if you assign the result. This is nice and is what we want. We don’t want extra object copies or memory allocations and deallocations.
Release
Things are a little bit better in release, but not by much. The optimizer seems to have figured out that it doesn’t really need to do 2 object copies, since it only ever wants at most one REAL copy of the object, so it gets away with doing one object copy in both situations instead of two.
That’s an improvement, but still not as good as the pre-increment case which hasn’t visibly changed in release (not sure about the assembly of these guys, but if you look and find something interesting, post a comment!)
Summary
As always, you should check your own assembled code, or test your compiler with printf output like this post does to ensure you really know what your code is doing.
But… it seems like you might want to use pre-increment if you ever use increment operators for heavy weight objects (such as iterators), but if you want to use post increment for integral types, it ought to be fine.
That said, a lot of people say “just use pre-increment” because at worst it’ll be the same as a post increment, but at best it will be a lot more efficient.
You do whatever you want, so long as you are aware of the implications of going one way or the other 😛
Pingback: Is pre-increment really faster than post increment? Part 1 | The blog at the bottom of the sea
Personally I believe that the post increment operator should “never” be used. I haven’t run across a reason to use it (but I have run across justifications for goto and the ternary operator).
The justification for only using pre increment isn’t a technical one but instead a social argument. First off there’s habit, which increment to you go to when incrementing a variable. If you’re constantly switching between post and pre depending upon variable type then at some point you’ll slip up and make a mistake. Second there is someone refactoring local code. You may be aware of the usage of post increments on an integer counter but if someone changes that to an iterator at a future point they may not notice every instance of post increment. Third there are global APIs and unknown effects. When calling a constructor it can fire off a lot of code; someone may have overloaded new and delete with their custom logger to print out all allocations, or you may have a Garbage Collector that has to handle all the allocations.
Summary: “There may not be a penalty for i++ vs ++i in today’s code but you can’t say the same about tomorrow’s code.”
LikeLike
seems the same way to go 😛
LikeLike