Skip to main content
GameDev.net gamedev.net
🔒 Locked

C++ - Properly designed function object

Started by gerbopel Feb 3, 2005 at 4:22 AM 9 replies 1.5k views
Original Post
gerbopel
gerbopel
Okay, so I had some tips on function objects yesterday that I'd like to follow through on hopefully. Assuming I have the following class: class CPerson { // for simplicity public: string Name; int Age; }; Now, I have a variable defined: vector People; How would you design a function object (it must be a function object) that could be used with find_if to search through the People to find the record with a particular name (designated at run-time). Morbo pointed out yesterday that I should avoid binary_function derivation, so that removes bind2nd as an option: find_if(People.begin(), People.end(), bind2nd(FuncObj(),"MyName")); So how would you do it without deriving from binary_function and using bind2nd. I agree that coming from binary_function is messy, with templates and everything, but I am not seeing a clean solution without bind2nd. Many thanks, Gerb
MaulingMonkey
MaulingMonkey
struct is_named{    std::string name;    is_named( const std::string & name )        : name( name )    {    }    bool operator()( const CPerson & person )    {        return person.name == name;    }};...std::vector< CPerson > People;...std::vector< CPerson >::iterator Mike = std::find_if( People.begin() , People.end() , is_named( "Mike" ) );
gerbopel
gerbopel
Cheers Monkey,

So I have two questions for you now:

1) Is your method considered the "right" way to implement a function object and my way considered "right" only if you will actually make use of the templates (i.e. overloaded == operator)? Of course, by not using my method, we cannot make use of the negator adapters. So that's one thing we lose, right?

2) To use your struct with the find_if, it'd go something like this (I'm not near a compiler right now):

is_named SearchName("Gerbopel");find_if(People.begin(), People.end(), SearchName);


p.s. How do I change the background color as everyone else seems to be doing for code listings?

[Edited by - gerbopel on February 3, 2005 5:43:19 AM]
MaulingMonkey
MaulingMonkey
Quote:
Original post by gerbopel
Cheers Monkey,

So I have two questions for you now:

1) Is your method considered the "right" way to implement a function object and my way considered "right" only if you will actually make use of the templates (i.e. overloaded == operator)? Of course, by not using my method, we cannot make use of the negator adapters. So that's one thing we lose, right?


What will be considered the "right" way by one will be considered the "wrong" way by others. If you wanted to be more perfectionistic about progamming contracts and whatnot, you'd probably inherit from std::unary_function (I think that's the class) but from my experience that's more hassle than necessary.

As for negator adapters, I believe this should work:

std::vector< CPerson >::iterator NotMike = std::find_if( Persons.begin() , Persons.end() , std::not1( is_named( "Mike" ) ) );


I'll run it through a compiler real quick though, I havn't fiddled with adapters much.

Quote:
2) To use your struct with the find_if, it'd go something like this (I'm not near a compiler right now):

is_named SearchName("Gerbopel");find_if(People.begin(), People.end(), SearchName);


Correct, if you scroll down in the source box I posted, you'll see an example usage, complete with creating an iterator named Mike, which should point to the first person found named "Mike".

Quote:
p.s. How do I change the background color as everyone else seems to be doing for code listings?


use [ source ] instead of [ code ] :).
MaulingMonkey
MaulingMonkey
Followup:

Aparently one does need to derive from unary_function in order for the negation adapter to work. Here's the full source of the example I got working:

#include <string> //string#include <vector> //vector#include <algorithm> //find_if#include <functional> //unary_function, not1#include <iostream> //cout, endlstruct CPerson{    std::string name;    CPerson( const std::string & name )        : name( name )    {    }};struct is_named : public std::unary_function< const CPerson , bool >{    std::string name;    is_named( const std::string & name )        : name( name )    {    }    bool operator()( const CPerson & person ) const    {        return person.name == name;    }};int main ( int argc , char ** argv ){    std::vector< CPerson > People;    People.push_back( CPerson( "Doug" ) );    People.push_back( CPerson( "Mike" ) );    std::vector< CPerson >::iterator Mike = std::find_if( People.begin() , People.end() , is_named( "Mike" ) );    std::vector< CPerson >::iterator NotMike = std::find_if( People.begin() , People.end() , std::not1( is_named( "Mike" ) ) );    std::cout << "Mike->name == " << Mike->name << std::endl;    std::cout << "NotMike->name == " << NotMike->name << std::endl;}


Result:

Mike->name == MikeNotMike->name == Doug
gerbopel
gerbopel
Doh! Didn't notice the scrollbar in the source window. Hey man, I greatly appreciate you taking the time out to help me. Thanks a bunch.

Gerb
Morbo
Morbo
There's no need to derive from anything to make a function adapter. All that you need is to create the proper typedefs. Classes like 'binary_function' and 'unary_function' are not part of the STL; they are part of the implementation and should not be used by clients.

To make a function object adaptable you simply define the following typedefs, depending on the type of adaptable you want:

Unary Adapter - create a typedef for 'result_type' and 'argument_type'
Binary Adapter - create a typedef for 'result_type', 'first_argument_type', and 'second_argument_type'

Knowing this, you can simplify your implementation of 'is_named' and make use of bind*:

struct is_named{    typedef bool result_type;    typedef const CPerson& first_argument_type;    typedef string second_argument_type    bool operator()( const CPerson & person, string name )    {        return person.name == name;    }};find_if(People.begin(),People.end(),bind2nd(is_named(),string("MyName")));
Fruny
Fruny
Quote:
Original post by Morbo
Classes like 'binary_function' and 'unary_function' are not part of the STL; they are part of the implementation and should not be used by clients.


Incorrect, they are mandated by the C++ Standard (Clause 20.3.1 p361 [lib.base]).

Do use them.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Morbo
Morbo
Quote:
Original post by Fruny
Quote:
Original post by Morbo
Classes like 'binary_function' and 'unary_function' are not part of the STL; they are part of the implementation and should not be used by clients.


Incorrect, they are mandated by the C++ Standard (Clause 20.3.1 p361 [lib.base]).

Do use them.


I stand corrected. And now we continue with your regularly scheduled programming.
Enigma
Enigma
Quote:
Original post by Morbo
There's no need to derive from anything to make a function adapter. All that you need is to create the proper typedefs. Classes like 'binary_function' and 'unary_function' are not part of the STL; they are part of the implementation and should not be used by clients.


Both binary_function and unary_function appear to be in the final draft C++ standard (20.3.1.1). Scott Meyers and Herb Sutter definitely don't seem to consider them as something to be avoided. Certainly you don't need them, but they don't hurt in any way.

Enigma

EDIT: Beaten to it by Fruny (now there's a surprise [lol])
MaulingMonkey
MaulingMonkey
Quote:
Original post by gerbopel
Doh! Didn't notice the scrollbar in the source window. Hey man, I greatly appreciate you taking the time out to help me. Thanks a bunch.

Gerb


No problem. The scrollbar is more apparent when there's more than 1 line of code hidden :).

Quote:
Original post by Morbo
There's no need to derive from anything to make a function adapter. All that you need is to create the proper typedefs. Classes like 'binary_function' and 'unary_function' are not part of the STL; they are part of the implementation and should not be used by clients.

There's no need to use anything but assembly. There's good reasons for inheriting from unary_function and binary_function, however. Besides doing the typedefs for you (in a less verbose way than manually declaring them, as they are a bit verbose) they also clearly state what the functor is (a functor). And as allready mentioned, '*_function' is part of the standard. Even if it wern't, I'd still use them, because in the worst case scenario (pretending it isn't standard, and lacking on my compiler) I'd just be rolling my own copy of it anyways.

Quote:
Knowing this, you can simplify your implementation of 'is_named' and make use of bind*:

*** Source Snippet Removed ***


That's a simplification??? You've got to be joking. I prefer to have my using code be:

is_named("Mike")


to:

bind2nd(is_named(),"MyName")


any day. Sure, you've removed the constructor of the class, but you've also added more verbose usage, a lengthier implementation by manually doing what inheriting from binary_function does for you, and the inability to store a temporary copy in a trivial manner. We go from:

is_named is_named_mike("Mike");


to:

std::string name_of_mike( "Mike" ); //we have to store seperately since binder2nd holds by reference, not valueis_named is_named_copy(); //again, because binder2nd holds by ref, not valuestd::binder2nd< is_named , std::string >( is_named_copy , name_of_mike ) is_named_mike; //we get to specify our template arguments as well here, joy!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.