Subj : Re: Can you mimic atexit() for class functions? To : borland.public.cpp.borlandcpp From : "Benjamin Pratt" Date : Mon Oct 13 2003 08:27 pm > wow. didn't know my question would start a war... me neither (frankly, i would assume that if one really wanted an "atexit" > look-alike, one would simply put their "clean up" code appropriate for > each object in the destructor...which is what should be happening anyway). right > my own attempt at creating this "callback" chain was what i guess i > would characterize as the somewhat brutish "C" version of your > mechanism. that is, i avoided the flurry of improper cast errors that > were generated when i tried the straightforward (for "C") > pass-a-pointer-to-my-function version of an AddCallback() function by > creating a CallbackList class that contained a virtual "void Callback()" > member function, and then made all classes that were to utilize this > mechanism inherit that CallbackList class. this allowed the interface > to use a common CallbackList "this" pointer. Good thinking. Certainly a more OO approch to the problem. Although there is obviously not a one for one relationship between callbacks in C and virtual functions in C++, they are distant cousins. A callback may be used to notify subscribers of an event. Similarly, a virtual function is often used by a ancestor object to notify a decendant of an event. Other infrences can be drawn as well. Inheriting from a callback interface class does more than just avoid the casting errors. You are sure that the object in your list derives from the callback interface. 'this' is valid and not anything else but what you expect it to be. That guarentee is not present when using void*'s to objects, like in the first example. > however, i wasn't savvy enough to use the two-layer approach you did > using a vector. i'm too new to "real" C++, so i just had my > CallbackList class contain a "CallbackList *pNext" member as well, so > that the constructor could automatically add the new "this" pointer to a > linked-list of pNext's and the destructor could excise it from that list. well, one-layer vs. two layer is probably more a matter of preference than anything else. I would recommend using the STL whenever possible to implement containers. There really is no reason to implement your own fundamental containers in C++ except in the rarest of circumstances, and even then it would be best to maintain compatibility with the STL. As you noted, removing the pointer from the list when the object destructs is vital to prevent dangling pointers, and should have been included in my examples. > the downside of my method (from my limited perspective, that is, as i > didn't really recognize the "safety" issues) was that the callback > functions in each object had to have the same name (e.g., "Callback()") > since the "this" pointer was simply used to call the appropriate > function by its fixed member function name. i note that this same > limitation exists in your method, which replaces my "C" style > linked-list mechanism with what i take to be a more "C++" style use of > vectors. The function name limitation is not a problem in the first example, but as stated earlier, is more prone to error. > if the question is not too stupid, I'm sure we all had a grade school teacher that said, "No question is stupid!" > maybe you could give me the > reader's digest condensed version as to the benefit of using the > two-class method you used? ultimatly, it's just my preference, but IMHO: 1. Conceptually, I don't think of CallbackInterface as being 'part' of the object it exposes, just an interface. Hence I want it to have the smallest footprint on the object possible. Just a ctor/dtor and a pure virtual function. If I adding data members to CallbackInterface, it makes the derived object larger in size. This is partly unavoidable, because CallbackInterface will add a vtable to the object if it is not already derived from anything else. 1a. The callback function is 'pure virtual' so that the derived function is forced to define Callback(). 2. Your implementation does not suffer from item 1 either, because *pNext would have to be static, thus it is not adding anything to the size of the object, but I prefer to avoid static members when possible. Especially when a significant amount of logic is being performed on them. Often it is better to use a singleton which CallbackContainer would probably be implemented as. 3. The work of storing pointers to objects, processing the callbacks, recording results, etc. etc. etc is completly different from what the 'object' pointed to is or does. Hence two separte objects. The objects support callbacks through a common 'interface' by deriving from CallbackInterface, but that is as far as they need to go in implementing it. What CallbackContainer does with the interface pointer is not their concern, all they care about is servicing requests made though that interface. In OO theory, an interface is not really thought of as an object, it is a seperate construct. Some OO languages use seperate keywords to create 'interfaces' and 'objects'. As you can see, the reason I choose to seperate them is due to how I viewed them rather than a hard and fast rule. Much of the 'rules' in C++ are actually 'best practices'. When I was comming to C++ from C, I would have done exactly as you had. Today, I didn't even occur to me not to seperate them. As OO design becomes more familiar, these 'best practices' will fall into place. Of course, this is all just my opinion, and I welcome any support/dissent. > also, one reason i didn't like the naming > limitation was the inability to re-use the same callback class for two > different purposes; that is, for two different chains of callbacks, say, > SomeArbitraryFunc1() and SomeArbitraryFunc2(). if i understand your > method correctly, the best way i could do this would be to make the > CallbackInterface class contain both a virtual Callback1() and > Callback2() member function, and have DoCallbacks1() and DoCallbacks2() > functions to call each list? That is one way, but there are others. :) How about a list of CALLBACK lists in the container and the interface constructor can tell it which list to add itself to? Or several containers each containing one list and the interface constructor can take the container as a parameter? You'll have to weigh the merits of these and other options to decide which is best for you. > alas, otherwise it does seem as if the one thing i wanted to avoid is > unavoidable (the ability to chain arbitrary "void (*)()" callbacks the > way one would in "C"). Well, the first example is a way to do that, but it violates the C++ 'best practice' of type safety. > in "C", one would likely just use a callback with the form > "void (*pfn)(void *amorphous_thing)" to allow the arbitrary action/verb > "pfn" to operate on the arbitrary object/subject "amorphous_thing". Well now you are describing C++ templates, but that's a whole seperate conversation ;) Regards, Ben .