tags:

views:

616

answers:

3

I am following a book to learn C++ (come from a python background). I've written this, which works:

class CatalogueItem
{
    public:
        CatalogueItem();
        CatalogueItem(int item_code, const string &name, const string &description);
        ~CatalogueItem() {};

        bool operator< (const CatalogueItem &other) const;
        ...

    private:
        ...
};

...

list<CatalogueItem> my_list;

// this is just me playing around
CatalogueItem items[2];
items[0] = CatalogueItem(4, string("box"), string("it's a box"));
items[1] = CatalogueItem(3, string("cat"), string("it's a cat"));

my_list.push_back(items[0]);
my_list.push_back(items[1]);

my_list.sort();

The part I'm trying out is using the operator < to allow the list to sort itsself.

This all seems good, but http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Operator%5FOverloading seems to suggest avoiding doing this, which is exactly what the book says to do! ("In particular, do not overload operator== or operator< just so that your class can be used as a key in an STL container; instead, you should create equality and comparison functor types when declaring the container.")

I understand "create equality and comparison functor types" to mean creating comparison functions, like the below one:

bool my_comparison_function(const CatalogueItem &a, const CatalogueItem &b)
{
    // my comparison code here
}

Is that what the style guide is referring to?

Does anyone have an option as to which method is more "correct"?

J

+10  A: 

A functor type would be more like this:

struct CatalogueItemLessThan
{
    bool operator()(const CatalogueItem &a, const CatalogueItem &b)
    {
    }    
};

Then the usage would look like this:

list<CatalogueItem> my_list;

// this is just me playing around
CatalogueItem items[2];
items[0] = CatalogueItem(4, string("box"), string("it's a box"));
items[1] = CatalogueItem(3, string("cat"), string("it's a cat"));

my_list.push_back(items[0]);
my_list.push_back(items[1]);

my_list.sort(CatalogueItemLessThan());

The main advantage of this, is that is allows you to decouple sorting from the object itself. You can now provide as many types of sorting as you want, and use them in different places. (For example, string can be sorted in lexical order, or case-insensitively, or "naturally".

The advantage of using a functor as opposed to a loose function is that you can pass parameters into the comparison to modify how the functor should behave.

In general, the Google style-guide is not really the best style guide out there (IMHO especially their taking exception to exceptions, but that's another discussion). If an object has an obvious sorting order, I often add in a default operator<. If later, there are extra sort orders I want to add, then I add in loose functions. If at a later time, I need to add parameters to the sort order, then I make them into functors. There's no sense in adding in complexity before it's needed.

Eclipse
Yep, this is what they mean; such classes are sometimes known as comparators.
David Seiler
The other main advantage of using a functor is that you can use a functor as a type parameter to templates such as `std::map`, which you cannot do with a plain function.
Pavel Minaev
Good, but absolutely incorrect! :) You _can_ use ordinary functions as parameters to template classes. Ordinary functions (decaying to function pionters) are Functors as well, and they work perfectly well as comparators with 'std::map' (for example). Just try it. The main benefit of a class-based functor is that you can attach an internal state to that functor, i.e. add data fields to the class. Ordinary functions can't have such a state attached to them. Otherwise, there's no difference between class-based functors and ordinary functions.
AndreyT
The other advantage of functors is that they can be inlined into the sorting code, whereas function pointers must always make the function call.
Greg Rogers
@Greg Rogers: That's true (ignoring what some super-advanced compiler can do). Also, class-based functors sometimes are more resistant to ambiguities in overload resolution. However, this doesn't change the fact that ordinary functions are Functors as well and work fine wherever Functors are allowed.
AndreyT
@AndreyT: I haven't looked it up in the standard, but comeau doesn't let you use a free function as the predicate type in std::map: "ComeauTest.c", line 20: error: function "testLess" is not a type name map<int, int, testLess> intMap;
Eclipse
That's not how you use it. You have to pass a _function pointer type_ as the type argument to the template, of course. The function pointer itself is passed as an argument to the contructor. For example: std::map<int, int, bool (*)(int, int)> map(testLess)
AndreyT
Ahh - that makes sense, thanks.
Eclipse
+1. Google is right imho: "In particular, do not overload operator== or operator< **just** so that your class can be used as a key in an STL container"
Johannes Schaub - litb
+1  A: 

A functor type is a C++ type (class or struct), that overloads the () operator so that instances of the type behave like a function. This is similar to a class implementing __call__() in Python.

Some STL collection types like std::map require a key_compare functor to order the keys in interal tree structures and thus providing fast access times. By default, this is std::less, which uses operator< to compare values. Therefore this operator is often provided to allow custom classes to act as keys in std::map (and similar).

Google obviously discourages this in favor of supplying your own comparison functor. So, instead of implementing operator<, you could do the following:

struct my_compare
{
    bool operator ()(const CatalogueItem& lhs, const CatalogueItem& rhs)
    {
        ...
    }
};

If you must access private members to implement this, declare the functor as a friend of your class.

Ferdinand Beyer
Functor is a more generic concept than what you describe above. Functor is everything that accepts the postfix '()' operator. Ordinary function or function pointer is also a functor. It doesn't have to be an object of class type with overloaded '()'.
AndreyT
However, just to nit-pick, I think Ferdinand is right in defining the term "functor type". He didn't say "a functor is".+1 for adding the friend recommendation!
Andy Dent
+7  A: 

What Google is trying to say to you is the following.

As you know, you can overload one and only one operator '<' for a given type. Let's say it works for you. But imagine that in the future you might need to sort objects of the same type in accordance with some other comparison criterion. How are you going to do that? The only available version of '<' is already taken.

Of course, you can do that by writing a new named comparison function/functor (not the '<' operator) and explicitly supplying it to the sorting algorithm. You can write 2, 5, 10 more of them. You can write as many as you want. It will work. However, at that point there will be an obvious asymmetry in your code. One comparison function is implemented as 'operator <'. The others - as different named functions/functors. Is there a good reason for this asymmetry?

Well, there might be. If you have a very well-defined and obvious natural sorting method that applies to your type, it makes a very good sense to implement it as operator '<'. This would be the main comparison method. And other, auxiliary, less "natural" comparison methods can and should be implemented as named functions. This is prefectly fine.

However, what if you don't have such an obvious candidate for the "natural" comparison? In this case favoring one method over the other and "wasting" the '<' operator on an arbitrarily chosen one is not a good idea. In this case it is recommended to leave the '<' alone, and stick to named functions/functors instead.

In other words, by overloading the '<' you create a "favorite" comparison for the given type. If that's what you really want - go ahead and do it. But keep in mind that in many cases creating an artificial and arbitrary "favorite" is not a good idea. Don't rush the process of choosing that favorite. Don't take the '<' too early.

AndreyT
Good points on why this is a recommendation. On the other hand, many types only ever need to be sorted one way. It's not too difficult to switch from using operator< to a functor if you own all the code, the main difficulty is if you are producing a library for others to use and can't just bounce changes off the compiler.
tfinniga