1. Example of Prefix/Postfix Increment Operators
The following example demonstrates the implementation of prefix and postfix increment operators for a custom Integer class.
class Integer
{
public:
// ++i: first increment, then return the new value
Integer &operator++()
{
value_ += 1;
return *this;
}
// i++: first save the old value, then increment, finally return the old value
Integer operator++(int)
{
Integer old = *this;
value_ += 1;
return old;
}
private:
int value_;
};
2. Performance Tests on Built-in and User-Defined Types
The following code tests the performance difference between prefix and postfix increment on both int and std::vector iterators.
#include <iostream>
#include <vector>
#include <windows.h>
int main()
{
const int sizeInt = 0x00fffffe;
const int sizeVec = 0x000ffffe;
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
// Test on int array using postfix increment
{
int* testValue = new int[sizeInt];
LARGE_INTEGER start, stop;
QueryPerformanceCounter(&start);
for (int i = 0; i < sizeInt; ++i)
{
testValue[i]++;
}
QueryPerformanceCounter(&stop);
const auto interval = static_cast<double>(stop.QuadPart - start.QuadPart);
const auto timeSpan = interval / frequency.QuadPart * 1000.0; // milliseconds
std::cout << "i++ " << sizeInt << " times takes " << timeSpan << "ms." << std::endl;
delete[] testValue;
}
// Test on int array using prefix increment
{
int* testValue = new int[sizeInt];
LARGE_INTEGER start, stop;
QueryPerformanceCounter(&start);
for (int i = 0; i < sizeInt; ++i)
{
++testValue[i];
}
QueryPerformanceCounter(&stop);
const auto interval = static_cast<double>(stop.QuadPart - start.QuadPart);
const auto timeSpan = interval / frequency.QuadPart * 1000.0; // milliseconds
std::cout << "++i " << sizeInt << " times takes " << timeSpan << "ms." << std::endl;
delete[] testValue;
}
// Test on vector iterator using postfix increment
{
const std::vector<int> testVec(sizeVec);
LARGE_INTEGER start, stop;
QueryPerformanceCounter(&start);
for (auto iter = testVec.cbegin(); iter != testVec.cend(); iter++)
{
}
QueryPerformanceCounter(&stop);
const auto interval = static_cast<double>(stop.QuadPart - start.QuadPart);
const auto timeSpan = interval / frequency.QuadPart * 1000.0; // milliseconds
std::cout << "iterator++ " << sizeVec << " times takes " << timeSpan << "ms." << std::endl;
}
// Test on vector iterator using prefix increment
{
const std::vector<int> testVec(sizeVec);
LARGE_INTEGER start, stop;
QueryPerformanceCounter(&start);
for (auto iter = testVec.cbegin(); iter != testVec.cend(); ++iter)
{
}
QueryPerformanceCounter(&stop);
const auto interval = static_cast<double>(stop.QuadPart - start.QuadPart);
const auto timeSpan = interval / frequency.QuadPart * 1000.0; // milliseconds
std::cout << "++iterator " << sizeVec << " times takes " << timeSpan << "ms." << std::endl;
}
return 0;
}
3. Results from Five Runs
The images below show the results from five consecutive runs on the same machine. (Note: Images are omitted in this text version for brevity, but the trend is consistent across all runs.)
- Run 1: [results]
- Run 2: [results]
- Run 3: [results]
- Run 4: [results]
- Run 5: [results]
4. Analysis and Conclusion
From test results, we can observe:
- For built-in
inttype, the time taken by prefix increment (++i) and postfix increment (i++) is nearly identical. - For
std::vectoriterators, prefix increment (++iter) takes roughly half the time of postfix increment (iter++).
This difference arises because postfix increment must:
- Create a copy of the original object.
- Increment the original object.
- Return the saved copy (the old value).
Each postfix operation thus involves constructing a temporary object, which adds overhead for user-defined types. For built-in types, the compiler can often optimize away the copy, making the performance equal.
Conclusion:
- For C++ built-in types (like
int), prefix and postfix increment have negligible performance difference. - For user-defined types (classes, structs, or iterators), prefix increment is approximately twice as efficient as postfix increment.
5. Important Notes
The test loops above, if compiled in Release mode, may be optimized away entirely by the compiler because the results of the increment operations are not used. To observe the expected performance difference, the code should be compiled in Debug mode. However, in real-world applications, the compiler typically does not eliminate the increment operations if their results are actually used.