C++.Coding.Standards.1918.Rules.Guidelines [Electronic resources]

Herb Sutter, Andrei Alexandrescu

نسخه متنی -صفحه : 521/ 454
نمايش فراداده

Discussion

First, function objects are easy to make adaptable, and always should be (see Item 89). Even if you already have a function, sometimes you have to wrap it in

ptr_fun or

mem_fun anyway to add adaptability. For example, you have to do this in order to build up more complex expressions using binders (see also Item 84):

inline bool IsHeavy( const Thing& ) {

/*…*/ } find_if( v.begin(), v.end(),

not1( IsHeavy ) );

// error: isn't adaptable

The workaround is to insert

ptr_fun (or, for a member function,

mem_fun or

mem_fun_ref ):

inline bool IsHeavy( const Thing& ) {

/*…*/ } find_if( v.begin(), v.end(),

not1( ptr_fun<Thing,void>( IsHeavy ) ) );

// ok: now it's adaptable

Aside: Yes, it's a pain that here you need to explicitly specify

ptr_fun 's template arguments. This is another drawback to using functions. Briefly, the reason the template arguments are needed is that

ptr_fun deduces the argument and return types exactly and creates a

pointer_to_unary_function , which in turn helpfully tries to add another

& , and references to references are not currently allowed by ISO C++. There are ways in which

ptr_fun could, and probably should, be fixed so as to strip top-level

const and

& from non-pointer parameter and return types (see Item 89), but it doesn't do that today.Item 89), which is adaptable from the get-go without special syntax:

struct IsHeavy : unary_function<Thing, bool> { bool operator()( const Thing& ) const {

/*…*/ } }; find_if( v.begin(), v.end(),

not1( IsHeavy() ) );

// ok: adaptable

More importantly, you need a function object, not a function, to specify comparers for associative containers. This is because it's illegal to instantiate a template type parameter with a function type directly:

bool CompareThings( const Thing&, const Thing& ); set<Thing,

CompareThings > s;

// error

Instead, you need:

struct CompareThings : public binary_function<Thing,Thing,bool> { bool operator()( const Thing&, const Thing& ) const; }; set<Thing,

CompareThings > s;

// ok

Finally, there is also an efficiency benefit. Consider this familiar algorithm:

template<typename Iter, typename Compare> Iter find_if( Iter first, Iter last,

Compare comp );

If we pass a function as the comparer to

find_if

inline bool

Function ( const Thing& ) {

/*…*/ } find_if( v.begin(), v.end(),

Function );

we're actually passing a reference to

Function . Compilers rarely inline such function calls (except as part of whole-program analysis, which is still a relatively recent feature on popular compilers), even when as above the function is declared

inline and is visible while compiling the

find_if call. And, as noted, functions aren't adaptable.

If we pass a function object as the comparer to

find_if

struct FunctionObject : unary_function<Thing, bool> { bool

operator() ( const Thing& ) const {

/*…*/ } }; find_if( v.begin(), v.end(),

FunctionObject() );

we're passing an object that typically has an (implicitly or explicitly) inline

operator() function. Compilers have routinely inlined such calls since C++'s Bronze Age.

Note: This is not to encourage premature optimization (see Item 8), but to discourage premature pessimization (see Item 9). If you already have a function, go ahead and pass a pointer to the function (unless you have to wrap it with

ptr_fun or

mem_fun anyway). But if you're writing a new piece of code for use as an argument to an algorithm, prefer writing the extra boilerplate to make it a function object.