|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Russian TranslationI'm pleased to announce that Denis Bulichenko has translated this article into Russian! It has been published in RSDN. The complete article will soon be available on-line. I'm deeply indebted to you, Denis. IntroductionStandard C++ does not have true object-oriented function pointers. This is unfortunate, because object-oriented function pointers, also called 'closures' or 'delegates', have proved their value in similar languages. In Delphi (Object Pascal), they are the basis for Borland's Visual Component Library (VCL). More recently, C# has popularized the delegate concept, contributing to the success of that language. For many applications, delegates simplify the use of elegant design patterns (Observer, Strategy, State[GoF]) composed of very loosely coupled objects. There can be no doubt that such a feature would be useful in standard C++. Instead of delegates, C++ only provides member function pointers. Most C++ programmers have never used member function pointers, and with good reason. They have their own bizarre syntax (the In this article, I'll "lift the lid" on member function pointers. After a recap of the syntax and idiosyncrasies of member function pointers, I'll explain how member function pointers are implemented by commonly-used compilers. I'll show how compilers could implement delegates efficiently. Finally, I will show how I used this clandestine knowledge of member function pointers to make an implementation of delegates that is optimally efficient on most C++ compilers. For example, invoking a single-target delegate on Visual C++ (6.0, .NET, and .NET 2003) generates just two lines of assembly code! Function PointersWe begin with a review of function pointers. In C, and consequently in C++, a function pointer called float (*my_func_ptr)(int, char *); // To make it more understandable, I strongly recommend that you use a typedef. // Things can get particularly confusing when // the function pointer is a parameter to a function. // The declaration would then look like this: typedef float (*MyFuncPtrType)(int, char *); MyFuncPtrType my_func_ptr; Note that there is a different type of function pointer for each combination of arguments. On MSVC, there is also a different type for each of the three different calling conventions: my_func_ptr = some_func; When you want to invoke the function that you stored, you do this: (*my_func_ptr)(7, "Arbitrary String"); You are allowed to cast from one type of function pointer to another. But you are not allowed to cast a function pointer to a In C, the most common uses of function pointers are as parameters to library functions like Member Function PointersIn C++ programs, most functions are member functions; that is, they are part of a class. You are not allowed to use an ordinary function pointer to point to a member function; instead, you have to use a member function pointer. A member function pointer to a member function of class float (SomeClass::*my_memfunc_ptr)(int, char *); // For const member functions, it's declared like this: float (SomeClass::*my_const_memfunc_ptr)(int, char *) const; Notice that a special operator ( You make your function pointer point to a function my_memfunc_ptr = &SomeClass::some_member_func; // This is the syntax for operators: my_memfunc_ptr = &SomeClass::operator !; // There is no way to take the address of a constructor or destructor Some compilers (most notably MSVC 6 and 7) will let you omit the SomeClass *x = new SomeClass; (x->*my_memfunc_ptr)(6, "Another Arbitrary Parameter"); // You can also use the .* operator if your class is on the stack. SomeClass y; (y.*my_memfunc_ptr)(15, "Different parameters this time"); Don't blame me for the syntax -- it seems that one of the designers of C++ loved punctuation marks! C++ added three special operators to the C language specifically to support member pointers. A member function pointer can be set to 0, and provides the operators Weird Things about Member Function PointersThere are some weird things about member function pointers. Firstly, you can't use a member function to point to a class SomeClass { public: virtual void some_member_func(int x, char *p) { printf("In SomeClass"); }; }; class DerivedClass : public SomeClass { public: // If you uncomment the next line, the code at line (*) will fail! // virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); }; }; int main() { // Declare a member function pointer for SomeClass typedef void (SomeClass::*SomeClassMFP)(int, char *); SomeClassMFP my_memfunc_ptr; my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*) } Curiously enough, Casting between member function pointers is an extremely murky area. During the standardization of C++, there was a lot of discussion about whether you should be able to cast a member function pointer from one class to a member function pointer of a base or derived class, and whether you could cast between unrelated classes. By the time the standards committee made up their mind, different compiler vendors had already made implementation decisions which had locked them into different answers to these questions. According to the Standard (section 5.2.10/9), you can use On some compilers, weird stuff happen even when converting between member function pointers of base and derived classes. When multiple inheritance is involved, using class Derived: public Base1, public Base2 // case (a) class Derived2: public Base2, public Base1 // case (b) typedef void (Derived::* Derived_mfp)(); typedef void (Derived2::* Derived2_mfp)(); typedef void (Base1::* Base1mfp) (); typedef void (Base2::* Base2mfp) (); Derived_mfp x; For case (a), There's another interesting rule in the standard: you can declare a member function pointer before the class has been defined. You can even invoke a member function of this incomplete type! This will be discussed later in the article. Note that a few compilers can't cope with this (early MSVC, early CodePlay, LVMM). It's worth noting that, as well as member function pointers, the C++ standard also provides member data pointers. They share the same operators, and some of the same implementation issues. They are used in some implementations of Uses of Member Function PointersBy now, I've probably convinced you that member function pointers are somewhat bizarre. But what are they useful for? I did an extensive search of published code on the web to find out. I found two common ways in which member function pointers are used:
They also have trivial uses in one-line function adaptors in the STL and Boost libraries, allowing you to use member functions with the standard algorithms. In such cases, they are used at compile-time; usually, no function pointers actually appear in the compiled code. The most interesting application of member function pointers is in defining complex interfaces. Some impressive things can be done in this way, but I didn't find many examples. Most of the time, these jobs can be performed more elegantly with virtual functions or with a refactoring of the problem. But by far, the most famous uses of member function pointers are in application frameworks of various kinds. They form the core of MFC's messaging system. When you use MFC's message map macros (e.g., In my searches, I was unable to find many examples of good usage of member function pointers, other than at compile time. For all their complexity, they don't add much to the language. It's hard to escape the conclusion that C++ member function pointers have a flawed design. In writing this article, I have one key point to make: It is absurd that the C++ Standard allows you to cast between member function pointers, but doesn't allow you to invoke them once you've done it. It's absurd for three reasons. Firstly, the cast won't always work on many popular compilers (so, casting is standard, but not portable). Secondly, on all compilers, if the cast is successful, invoking the cast member function pointer behaves exactly as you would hope: there is no need for it to be classed as "undefined behavior". (Invocation is portable, but not standard!) Thirdly, allowing the cast without allowing invocation is completely useless; but if both cast and invocation are possible, it's easy to implement efficient delegates, with a huge benefit to the language. To try to convince you of this controversial assertion, consider a file consisting solely of the following code. This is legal C++. class SomeClass; typedef void (SomeClass::* SomeClassFunction)(void); void Invoke(SomeClass *pClass, SomeClassFunction funcptr) { (pClass->*funcptr)(); }; Note that the compiler has to produce assembly code to invoke the member function pointer, knowing nothing about the class To explain the other half of my assertion, that casting doesn't work the way the standard says it should, I need to discuss in detail exactly how compilers implement member function pointers. This will also help to explain why the rules about the use of member function pointers are so restrictive. Accurate documentation about member function pointers is hard to obtain, and misinformation is common, so I've examined the assembly code produced by a large range of compilers. It's time to get our hands dirty. Member Function Pointers - why are they so complex?A member function of a class is a bit different from a standard C function. As well as the declared parameters, it has a hidden parameter called You'd probably guess that a "member function pointer", like a normal function pointer, just holds a code pointer. You would be wrong. On almost all compilers, a member function pointer is bigger than a function pointer. Most bizarrely, in Visual C++, a member function pointer might be 4, 8, 12, or 16 bytes long, depending on the nature of the class it's associated with, and depending on what compiler settings are used! Member function pointers are more complex than you might expect. But it was not always so. Let's go back in time to the early 1980's. When the original C++ compiler (CFront) was originally developed, it only had single inheritance. When member function pointers were introduced, they were simple: they were just function pointers that had an extra This idyllic world was shattered when CFront 2.0 was released. It introduced templates and multiple inheritance. Part of the collateral damage of multiple inheritance was the castration of member function pointers. The problem is, with multiple inheritance, you don't know what class A { public: virtual int Afunc() { return 2; }; }; class B { public: int Bfunc() { return 3; }; }; // C is a single inheritance class, derives only from A class C: public A { public: int Cfunc() { return 4; }; }; // D uses multiple inheritance class D: public A, public B { public: int Dfunc() { return 5; }; }; Suppose we create a member function pointer for class Now, suppose we create a member function pointer for class If you're using virtual inheritance (i.e., virtual base classes), it's much worse, and you can easily lose your mind trying to understand it. Typically, the compiler uses a virtual function table ('vtable') which stores for each virtual function, the function address, and the virtual_delta: the amount in bytes that needs to be added to the supplied None of this complexity would exist if C++ had defined member function pointers a bit differently. In the code above, the complexity only comes because you are allowed to refer to Implementations of Member Function PointersSo, how do compilers typically implement member function pointers? Here are some results obtained by applying the
# Or 4,8, or 12 if the The compilers are Microsoft Visual C++ 4.0 to 7.1 (.NET 2003), GNU G++ 3.2 (MingW binaries, www.mingw.org), Borland BCB 5.1 (www.borland.com), Open Watcom (WCL) 1.2 (www.openwatcom.org), Digital Mars (DMC) 8.38n (www.digitalmars.com), Intel C++ 8.0 for Windows IA-32, Intel C++ 8.0 for Itanium (www.intel.com), IBM XLC for AIX (Power, PowerPC), Metrowerks Code Warrior 9.1 for Windows (www.metrowerks.com), and Comeau C++ 4.3 (www.comeaucomputing.com). The data for Comeau applies to all their supported 32-bit platforms (x86, Alpha, SPARC, etc.). The 16-bit compilers were also tested in four DOS configurations (tiny, compact, medium, and large) to show the impact of various code and data pointer sizes. MSVC was also tested with an option (/vmg) that gives "full generality for pointers to members". (If you have a compiler from a vendor that is not listed here, please let me know. Results for non-x86 processors are particularly valuable.) Amazing, isn't it? Looking at this table, you can readily see how easy it is to write code that will work in some circumstances but fail to compile in others. The internal implementations are obviously very different between compilers; in fact, I don't think that any other language feature has such a diversity of implementation. Looking in detail at the implementations reveals some surprising nastiness. The Well-Behaved CompilersFor almost all compilers, two fields I've called struct BorlandMFP { // also used by Watcom CODEPTR m_func_address; int delta; int vindex; // or 0 if no virtual inheritance }; if (vindex==0) adjustedthis = this + delta; else adjustedthis = *(this + vindex -1) + delta CALL funcadr If virtual functions are used, the function pointer points to a two-instruction 'thunk' to determine the actual function to be called. Borland applies an optimization: if it knows that the class only used single inheritance, it knows that Many other compilers use the same calculation, often with a minor reordering of the structure. // Metrowerks CodeWarrior uses a slight variation of this theme. // It uses this structure even in Embedded C++ mode, in which // multiple inheritance is disabled! struct MetrowerksMFP { int delta; int vindex; // or -1 if no virtual inheritance CODEPTR func_address; }; // An early version of SunCC apparently used yet another ordering: struct { int vindex; // or 0 if a non-virtual function CODEPTR func_address; // or 0 if a virtual function int delta; }; Metrowerks doesn't seem to inline the calculation. Instead, it's performed in a short 'member function invoker' routine. This reduces code size a little but makes their member function pointers a bit slower. Digital Mars C++ (formerly Zortech C++, then Symantec C++) uses a different optimization. For single-inheritance classes, a member function pointer is just the address of the function. When more complex inheritance is involved, the member function pointer points to a 'thunk' function, which performs the necessary adjustments to the struct DigitalMarsMFP { // Why doesn't everyone else do it this way? CODEPTR func_address; }; Current versions of the GNU compiler use a strange and tricky optimization. It observes that, for virtual inheritance, you have to look up the vtable in order to get the // GNU g++ uses a tricky space optimisation, also adopted by IBM's VisualAge and XLC. struct GnuMFP { union { CODEPTR funcadr; // always even int vtable_index_2; // = vindex*2+1, always odd }; int delta; }; adjustedthis = this + delta if (funcadr & 1) CALL (* ( *delta + (vindex+1)/2) + 4) else CALL funcadr The G++ method is well documented, so it has been adopted by many other vendors, including IBM's VisualAge and XLC compilers, recent versions of Open64, Pathscale EKO, and Metrowerks' 64-bit compilers. A simpler scheme used by earlier versions of GCC is also very common. SGI's now discontinued MIPSPro and Pro64 compilers, and Apple's ancient MrCpp compiler used this method. (Note that the Pro64 compiler has become the open source Open64 compiler). struct Pro64MFP { short delta; short vindex; union { CODEPTR funcadr; // if vindex==-1 short __delta2; } __funcadr_or_delta2; }; // If vindex==0, then it is a null pointer-to-member. Compilers based on the Edison Design Group front-end (Comeau, Portland Group, Greenhills) use a method that is almost identical. Their calculation is (PGI 32-bit compilers): // Compilers using the EDG front-end (Comeau, Portland Group, Greenhills, etc) struct EdisonMFP{ short delta; short vindex; union { CODEPTR funcadr; // if vindex=0 long vtordisp; // if vindex!=0 }; }; if (vindex==0) { adjustedthis=this + delta; CALL funcadr; } else { adjustedthis = this+delta + *(*(this+delta+vtordisp) + vindex*8); CALL *(*(this+delta+funcadr)+vindex*8 + 4); }; Most compilers for embedded systems don't allow multiple inheritance. Thus, these compilers avoid all the quirkiness: a member function pointer is just a normal function pointer with a hidden ' The Sordid Tale of Microsoft's "Smallest For Class" MethodMicrosoft compilers use an optimization that is similar to Borland's. They treat single inheritance with optimum efficiency. But unlike Borland, the default is to discard the entries which will always be zero. This means that single-inheritance pointers are the size of simple function pointers, multiple inheritance pointers are larger, and virtual inheritance pointers are larger still. This saves space. But it's not standard-compliant, and it has some bizarre side-effects. Firstly, casting a member function pointer between a derived class and a base class can change its size! Consequently, information can be lost. Secondly, when a member function is declared before its class is defined, the compiler has to work out how much space to allocate to it. But it can't do this reliably, because it doesn't know the inheritance nature of the class until it's defined. It has to guess. If it guesses wrongly in one source file, but correctly in another, your program will inexplicably crash at runtime. So, Microsoft added some reserved words to their compiler: The documentation implies that specifying /vmg is equivalent to declaring every class with the // Microsoft and Intel use this for the 'Unknown' case. // Microsoft also use it when the /vmg option is used // In VC1.5 - VC6, this structure is broken! See below. struct MicrosoftUnknownMFP{ FunctionPointer m_func_address; // 64 bits for Itanium. int m_delta; int m_vtordisp; int m_vtable_index; // or 0 if no virtual inheritance }; if (vindex=0) adjustedthis = this + delta else adjustedthis = this + delta + vtordisp + *(*(this + vtordisp) + vindex) CALL funcadr In the virtual inheritance case, the Intel uses the same calculation as MSVC, but their /vmg option behaves quite differently (it has almost no effect - it only affects the unknown_inheritance case). The release notes for their compiler state that conversions between pointer to member types are not fully supported in the virtual inheritance case, and warns that compiler crashes or incorrect code generation may result if you try. This is a very nasty corner of the language. And then there's CodePlay. Early versions of Codeplay's VectorC had options for link compatibility with Microsoft VC6, GNU, and Metrowerks. But, they always used the Microsoft method. They reverse-engineered it, just as I have, but they didn't detect the What have we learned from all of this?In theory, all of these vendors could radically change their technique for representing MFPs. In practice, this is extremely unlikely, because it would break a lot of existing code. At MSDN, there is a very old article that was published by Microsoft which explains the run-time implementation details of Visual C++ [JanGray]. It's written by Jan Gray, who actually wrote the MS C++ object model in 1990. Although the article dates from 1994, it is still relevant - excluding the bug fix, Microsoft hasn't changed it for 15 years. Similarly, the earliest compiler I have (Borland C++ 3.0, (1990)) generates identical code to Borland's most recent compiler, except of course that 16 bit registers are replaced with 32 bit ones. By now, you know far too much about member function pointers. What's the point? I've dragged you through this to establish a rule. Although these implementations are very different from one another, they have something useful in common: the assembly code required to invoke a member function pointer is identical, regardless of what class and parameters are involved. Some compilers apply optimizations depending on the inheritance nature of the class, but when the class being invoked is of incomplete type, all such optimizations are impossible. This fact can be exploited to create efficient delegates. DelegatesUnlike member function pointers, it's not hard to find uses for delegates. They can be used anywhere you'd use a function pointer in a C program. Perhaps most importantly, it's very easy to implement an improved version of the Subject/Observer design pattern [GoF, p. 293] using delegates. The Observer pattern is most obviously applicable in GUI code, but I've found that it gives even greater benefits in the heart of an application. Delegates also allow elegant implementations of the Strategy [GoF, p. 315] and State [GoF, p. 305] patterns. Now, here's the scandal. Delegates aren't just far more useful than member function pointers. They are much simpler as well! Since delegates are provided by the .NET languages, you might imagine that they are a high-level concept that is not easily implemented in assembly code. This is emphatically not the case: invoking a delegate is intrinsically a very low-level concept, and can be as low-level (and fast) as an ordinary function call. A C++ delegate just needs to contain a mov ecx, [this]
call [pfunc]
However, there's no way to generate such efficient code in standard C++. Borland solves this problem by adding a new keyword ( Interestingly, in C# and other .NET languages, a delegate is apparently dozens of times slower than a function call (MSDN). I suspect that this is because of the garbage collection and the .NET security requirements. Recently, Microsoft added a "unified event model" to Visual C++, with the keywords Motivation: The need for extremely fast delegatesThere is a plethora of implementations of delegates using standard C++. All of them use the same idea. The basic observation is that member function pointers act as delegates -- but they only work for a single class. To avoid this limitation, you add another level of indirection: you can use templates to create a 'member function invoker' for each class. The delegate holds the There are many implementations of this scheme, including several here at CodeProject. They vary in their complexity, their syntax (especially, how similar their syntax is to C#), and in their generality. The definitive implementation is boost::function. Recently, it was adopted into the next version of the C++ standard [Sutter1]. Expect its use to become widespread. As clever as the traditional implementations are, I find them unsatisfying. Although they provide the desired functionality, they tend to obscure the underlying issue: a low-level construct is missing from the language. It's frustrating that on all platforms, the 'member function invoker' code will be identical for almost all classes. More importantly, the heap is used. For some applications, this is unacceptable. One of my projects is a discrete event simulator. The core of such a program is an event dispatcher, which calls member functions of the various objects being simulated. Most of these member functions are very simple: they just update the internal state of the object, and sometimes add future events to the event queue. This is a perfect situation to use delegates. However, each delegate is only ever invoked once. Initially, I used I don't always (often?) get what I want, but this time I was lucky. The C++ code I present here generates optimal assembly code in almost all circumstances. Most importantly, invoking a single-target delegate is as fast as a normal function call. There is no overhead whatsoever. The only downside is that, to achieve this, I had to step outside the rules of standard C++. I used the clandestine knowledge of member function pointers to get this to work. Efficient delegates are possible on any C++ compiler, if you are very careful, and if you don't mind some compiler-specific code in a few cases. The trick: casting any member function pointer into a standard formThe core of my code is a class which converts an arbitrary class pointer and arbitrary member function pointer into a generic class pointer and a generic member function. C++ doesn't have a 'generic member function' type, so I cast to member functions of an undefined Most compilers treat all member function pointers identically, regardless of the class. For most of them, a straightforward Since we know how the compiler stores member function pointers internally, and because we know how the How can we distinguish between the different inheritance types? There's no official way to find out whether a class used multiple inheritance or not. But there's a sneaky way, which you can see if you look at the table I presented earlier -- on MSVC, each inheritance style produces a member function pointer with a different size. So, we use template specialization based on the size of the member function pointer! For multiple inheritance, it's a trivial calculation. A similar, but much nastier calculation is used in the unknown_inheritance (16 byte) case. For Microsoft's (and Intel's) ugly, non-standard 12 byte Once you can cast any class pointer and member function pointer into a standard form, it's easy (albeit tedious) to implement single-target delegates. You just need to make template classes for all of the different numbers of parameters. A very significant additional benefit of implementing delegates by this non-standard cast is that they can be compared for equality. Most existing delegate implementations can't do this, and this makes it difficult to use them for certain tasks, such as implementing multi-cast delegates [Sutter3]. Static functions as delegate targetsIdeally, a simple non-member function, or a static member function, could be used as a delegate target. This can be achieved by converting the static function into a member function. I can think of two methods of doing this, in both of which the delegate points to an 'invoker' member function which calls the static function. The Evil method uses a hack. You can store the function pointer instead of the The Safe method is to store the function pointer as an extra member of the delegate. The delegate points to its own member function. Whenever the delegate is copied, these self-references must be transformed, and this complicates the I've implemented both methods, because both have merit: the Safe method is guaranteed to work, and the Evil method generates the same ASM code that a compiler would probably generate if it had native support for delegates. The Evil method can be enabled with a Footnote: Why does the evil method work at all? If you look closely at the algorithm that is used by each compiler when invoking a member function pointer, you can see that for all those compilers, when a non-virtual function is used with single inheritance (ie Using the codeThe source code consists of the FastDelegate implementation (FastDelegate.h), and a demo .cpp file to illustrate the syntax. To use with MSVC, create a blank console app, and add these two files to it. For GNU, just type "g++ demo.cpp" on the command line. The fast delegates will work with any combination of parameters, but to make it work on as many compilers as possible, you have to specify the number of parameters when declaring the delegate. There is a maximum of eight parameters, but it's trivial to increase this limit. The namespace
Unlike most other implementations of delegates, equality operators ( Here's an excerpt from FastDelegateDemo.cpp which shows most of the allowed operations. using namespace fastdelegate; int main(void) { // Delegates with up to 8 parameters are supported. // Here's the case for a void function. // We declare a delegate and attach it to SimpleVoidFunction() printf("-- FastDelegate demo --\nA no-parameter delegate is declared using FastDelegate0\n\n"); FastDelegate0 noparameterdelegate(&SimpleVoidFunction); noparameterdelegate(); // invoke the delegate - this calls SimpleVoidFunction() printf("\n-- Examples using two-parameter delegates (int, char *) --\n\n"); typedef FastDelegate2<int, char *> MyDelegate; MyDelegate funclist[10]; // delegates are initialized to empty CBaseClass a("Base A"); CBaseClass b("Base B"); CDerivedClass d; CDerivedClass c; // Binding a simple member function funclist[0].bind(&a, &CBaseClass::SimpleMemberFunction); // You can also bind static (free) functions funclist[1].bind(&SimpleStaticFunction); // and static member functions funclist[2].bind(&CBaseClass::StaticMemberFunction); // and const member functions funclist[3].bind(&a, &CBaseClass::ConstMemberFunction); // and virtual member functions. funclist[4].bind(&b, &CBaseClass::SimpleVirtualFunction); // You can also use the = operator. For static functions, // a fastdelegate looks identical to a simple function pointer. funclist[5] = &CBaseClass::StaticMemberFunction; // The weird rule about the class of derived // member function pointers is avoided. // Note that as well as .bind(), you can also use the // MakeDelegate() global function. funclist[6] = MakeDelegate(&d, &CBaseClass::SimpleVirtualFunction); // The worst case is an abstract virtual function of a // virtually-derived class with at least one non-virtual base class. // This is a VERY obscure situation, which you're unlikely to encounter // in the real world, but it's included as an extreme test. funclist[7].bind(&c, &CDerivedClass::TrickyVirtualFunction); // ...BUT in such cases you should be using the base class as an // interface, anyway. The next line calls exactly the same function. funclist[8].bind(&c, &COtherClass::TrickyVirtualFunction); // You can also bind directly using the constructor MyDelegate dg(&b, &CBaseClass::SimpleVirtualFunction); char *msg = "Looking for equal delegate"; for (int i=0; i<10; i++) { printf("%d :", i); // The ==, !=, <=,<,>, and >= operators are provided // Note that they work even for inline functions. if (funclist[i]==dg) { msg = "Found equal delegate"; }; // There are several ways to test for an empty delegate // You can use if (funclist[i]) // or if (!funclist.empty()) // or if (funclist[i]!=0) // or if (!!funclist[i]) if (funclist[i]) { // Invocation generates optimal assembly code. funclist[i](i, msg); } else { printf("Delegate is empty\n"); }; } }; Non-void return valuesVersion 1.3 of the code adds the ability to have non-void return values. Like
I've got around it with two tricks:
There is one breaking change: all instances of Passing a FastDelegate as a function parameterThe // Accepts any function with a signature like: int func(double, double); class A { public: typedef FastDelegate2<double, double, int> FunctionA; void setFunction(FunctionA somefunc){ m_HiddenDelegate = somefunc; } private: FunctionA m_HiddenDelegate; }; // To set the delegate, the syntax is: A a; a.setFunction( MakeDelegate(&someClass, &someMember) ); // for member functions, or a.setFunction( &somefreefunction ); // for a non-class or static function Natural syntax and Boost compatibility (new to 1.4)Jody Hagins has enhanced the FastDelegateN classes to allow the same attractive syntax provided by recent versions of Jody also contributed a helper function, using boost::bind;
bind(&Foo:func, &foo, _1, _2);
we should be able to replace the " Ordered comparison operators (new to 1.4)
The DelegateMemento class (new to 1.4)A new class, const DelegateMemento GetMemento() const; void SetMemento(const DelegateMemento mem);
Implicit conversion to bool (new to 1.5)You can now use the The implementation was more difficult than expected. Merely supplying an LicenseThe source code attached to this article is released into the public domain. You may use it for any purpose. Frankly, writing the article was about ten times as much work as writing the code. Of course, if you create great software using the code, I would be interested to hear about it. And submissions are always welcome. PortabilityBecause it relies on behavior that is not defined by the standard, I've been careful to test the code on many compilers. Ironically, it's more portable than a lot of 'standard' code, because most compilers don't fully conform to the standard. It has also become safer through being widely known. The major compiler vendors and several members of the C++ Standards commitee know about the techniques presented here (in many cases, the lead compiler developers have contacted me about the article). There is negligible risk that vendors will make a change that would irreparably break the code. For example, no changes were required to support Microsoft's first 64 bit compilers. Codeplay has even used FastDelegates for internal testing of their VectorC compiler (it's not quite an endorsement, but very close). The
Here is the status of all other C++ compilers that I know of which are still in use:
And yet some people are still complaining that the code is not portable! (Sigh). ConclusionWhat started as an explanation of a few lines of code I'd written has turned into a monstrous tutorial on the perils of an obscure part of the language. I also discovered previously unreported bugs and incompatibilities in six popular compilers. It's an awful lot of work for two lines of assembly code! I hope that I've cleared up some of the misconceptions about the murky world of member function pointers and delegates. We've seen that much of the weirdness of member function pointers arise because they are implemented very differently by various compilers. We've also seen that, contrary to popular opinion, delegates are not a complicated, high-level construct, but are in fact very simple. I hope that I've convinced you that they should be part of the language. There is a reasonable chance that some form of direct compiler support for delegates will be added to C++ when the C++0x standard is released. (Start lobbying the Standards committee!) To my knowledge, no previous implementation of delegates in C++ is as efficient or easy to use as the FastDelegates I've presented here. It may amuse you to learn that most of it was programmed one-handed while I tried to get my baby daughter to sleep... I hope you find it useful. References
I've looked at dozens of websites while researching this article. Here are a few of the most interesting ones:
History
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||