Re: A solution for a fast std::list::size() *and* a fast std::list::splice()
Jerry Coffin wrote:
In article <fekgic$n0c$1@murdoch.acc.Virginia.EDU>, jkherciueh@gmx.net
says...
[ I had mentioned the rarity of examples of good uses for linked lists ]
What about this one:
[ code elided .... ]
A command_bag allows you to register an action. You get a ticket which
you can use to unregister the action. This can be useful if you have some
observers that want to be notified of certain events but may lose
interest at some point in the future.
The guarantees of std::list about invalidation of iterators are _exactly_
what you want in this case.
While I'll admit that in theory this is true, I also have to say that
when I've encountered similar situtations, it really didn't work out.
What you're doing is essentially equivalent to handing out a pointer to
the object's internal data, which we all know is generally a poor idea.
When I've encountered situations roughly similar to this, it ended up
being much cleaner to use a set. Instead of giving the client a unique
key in exchange for their function, when they register a function, you
return nothing. When they want to de-register the function, they just
tell you the function they want to de-register, and you look it up and
remove it from the set.
Theoretically, this should be marginally less efficient -- inserting and
removing items from a set is logarithmic instead of constant. Likewise,
traversing a set is theoretically marginally slower as well. In reality,
the difference between logarithmic and constant is usually quite small.
In addition, registering and unregistering functions doesn't happen
extremely often anyway. The difference in run-time speed wasn't even
dependably measureable, but the difference in the cleanliness of the
interface substantial and almost immediately obvious.
In the end, the validity of iterators matters primarily when you "hand
out" iterators to the object's data. I would posit that this is really
no different from handing out pointers to the object's data, which we've
all known for years is usually a bad idea (in fact, I believe that's the
theme of an item in one of Scott Meyers's books).
a) You got distracted by a minor issue that arose from code simplification
for the sake of exposition. In the real version, the return_ticket is not a
raw iterator but a class that contains that iterator (in fact, a shared
pointer to that iterator). The return_ticket will _not allow_ any access to
the registered object at all. It can _only_ be used to remove the object
from the bag (and in fact, it checks whether the object had been removed
before or whether the object is registered with a different command_bag and
triggers assertions for contract violations accordingly). Thus, it is not
at all comparable to handing out raw pointers.
Here is the actual version (rc_ptr is a reference counted pointer):
struct command_bag {
public:
typedef function< void(void) > command;
private:
typedef std::list< command > command_list;
command_list the_list;
static
command_list::iterator null ( void ) {
static command_list dummy;
return ( dummy.end() );
}
public:
class removal_ticket {
friend class command_bag;
rc_ptr< command_list::iterator > iter_ptr;
command_list::iterator list_end;
public:
removal_ticket ( command_list::iterator iter_a = null(),
command_list::iterator iter_b = null() )
: iter_ptr ( iter_a )
, list_end ( iter_b )
{}
};
template < typename Command >
removal_ticket insert ( Command c ) {
the_list.push_front( command( c ) );
return ( removal_ticket( the_list.begin(), the_list.end() ) );
}
void remove ( removal_ticket t ) {
ASSERT( *(t.iter_ptr) != null() );
ASSERT( t.list_end == the_list.end() );
the_list.erase( *(t.iter_ptr) );
*(t.iter_ptr) = null();
}
void execute ( void ) const {
for ( command_list::const_iterator iter = the_list.begin();
iter != the_list.end(); ++iter ) {
(*iter)();
}
}
};
b) The drawback of the approach using std::set<> is not so much that it is
marginally less efficient. The major drawback is that it imposes additional
conceptual requirements on the objects you can register: they must be
comparable with one another. This is generally not feasible for objects of
type tr1::function< void( whatever ) >.
Best
Kai-Uwe Bux