1e5c Subj : Re: Can you mimic atexit() for class functions? To : borland.public.cpp.borlandcpp From : gary Date : Tue Oct 14 2003 08:29 pm benjamin: thanks much for all your thoughts on this subject. hey, i think i might actually have learned something. C++ isn't the work of the devil afterall...just a god with a decidedly twisted sense of humor. :) thanks again. :gary Benjamin Pratt wrote: >>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 > > > . 0