The other day I came across a nice little C++-shoot-yourself-in-the-foot at one of our customers. Let’s see how fast you can spot the problem. The following code crashes with segmentation fault sometime, somewhere in the sort call (line 31).
#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
enum SORT_ORDER
{
SORT_ORDER_ASCENDING,
SORT_ORDER_DESCENDING
};
bool compareValues(const std::string& valueLeft,
const std::string& valueRight,
SORT_ORDER order)
{
const bool compareResult = (valueLeft < valueRight);
if (order == SORT_ORDER_DESCENDING) {
return !compareResult;
}
return compareResult;
}
int main(int argc, char *argv[])
{
std::vector<std::string> strValues(300);
std::fill(strValues.begin(), strValues.end(),
"Hallo");
std::sort(strValues.begin(), strValues.end(),
bind(compareValues, _1, _2, SORT_ORDER_DESCENDING));
return EXIT_SUCCESS;
}
Any ideas? The tricky thing about this bug is that the stacktrace output in the debugger gives absolutely no hint at all about its cause. And this is a simplified version of the real code which has to sort boost::shared_ptrs instead of strings. Believe me, you don’t want to see that stacktrace. Because of the use of boost::bind together with boost::shared_ptrs it looks, well, let’s say intimidating.
Still no idea?
I’ll give you a hint. If the SORT_ORDER is set to SORT_ORDER_ASCENDING everything is fine. …
Ok, the problem is that std::sort algorithm must be given a comparison function (object) that defines a strict weak ordering on the elements that are to be sorted. In other words the comparison function object must implement the ‘<‘ (less than) relationship on the elements.
Unfortunately, lines 20 to 22 break this ordering when SORT_ORDER_DESCENDING is given. The initial idea of this code was that, well, if compareResult gets returned on ascending sort order, lets just return the negation of it when the “negation” of acscending order is requested. This, of course, destroys the strict weak ordering requirement because whenever valueLeft == valueRight, the function returns true, meaning instead that valueLeft < valueRight. And this somehow wreaks havoc inside std::sort.
A better version of the function could be:
...
bool compareValues(const std::string& valueLeft,
const std::string& valueRight,
SORT_ORDER order)
{
// solution: return false independent of sort order
// whenever valueLeft == valueRight
if (valueLeft == valueRight) {
return false;
}
const bool compareResult = (valueLeft < valueRight);
if (order == SORT_ORDER_DESCENDING) {
return !compareResult;
}
return compareResult;
}
...
The really annoying thing about this whole issue is that std::sort just randomly crashes with a stack trace that shows nothing but some weird memory corruption going on. After the initial shock, this sends you down the complete wrong bug hunting road where you start looking for spots where memory could be overwritten or the like.
So beware of custom comparison functions or function objects. They might look innocent and easy, but they can give you lot’s of headaches.