BDE 4.14.0 Production release
|
Modules | |
bdlf_bind | |
Provide a signature-specific function object (functor). | |
bdlf_memfn | |
Provide member function pointer wrapper classes and utility. | |
bdlf_noop | |
Provide a functor class that does nothing. | |
bdlf_noop_cpp03 | |
Provide C++03 implementation for bdlf_noop.h. | |
bdlf_placeholder | |
Provide a parameterized placeholder and specialized placeholders. | |
Provide signature-specific function objects (functors).
Basic Development Library Functors (bdlf)
The 'bdlf' package provides components to implement function objects (functors) that return void and take between zero and nine arguments of arbitrary type. Function objects created by 'bdlf' components can be invoked in a manner similar to that of the following free function:
where 'arglist' contains between zero and nine arguments. Functor classes are differentiated by the number and type of arguments they expect. The 'bdlf' package contains 10 separate components that differ in the number of arguments their functors expect; within each component, templates are used to support variations in argument type.
In addition, three components (bdlf_bind , bdlf_memfn and bdlf_function ) provide more general functors that can conform to a specified prototype and return an arbitrary type, and that can be invoked in the same manner as before. The functors created by these two components currently support from zero up to 14 arguments. bdlf_bind provides compile-time polymorphism, and adapts an invocable object so that it conforms to a different interface and can be invoked with fewer arguments and/or with the arguments in a different order. This transformation is type-safe, in that type violations provoke compilation errors. bdlf_memfn provides a wrapper that allows a member function to be invoked like a free function, either by providing the object instance as first parameter, or by wrapping the object instance inside the function object. bdlf_function provides run-time polymorphism and is especially suited for callbacks because it adapts any invocable with a compatible prototype to a specified callback interface, at run-time and without the need for recompilation.
The 'bdlf' package currently has 5 components having 3 levels of physical dependency. The list below shows the hierarchical ordering of the components. The order of components within each level is not architecturally significant, just alphabetical.
bdlf_bind : Provide a signature-specific function object (functor).
bdlf_memfn : Provide member function pointer wrapper classes and utility.
bdlf_noop : Provide a functor class that does nothing.
'bdlf_noop_cpp03': !PRIVATE! Provide C++03 implementation for bdlf_noop.h
bdlf_placeholder : Provide a parameterized placeholder and specialized placeholders.
Package 'bdlf' is designed to use three other packages in 'bdl'. 'bdlf' provides the primary interface that a client will see, and the other packages play supporting roles.
The packages are:
'bdlf': Provides functors to clients in a canonical form.
'bdlfr': This package is DEPRECATED. A common reference-counted base class, used by 'bdlfi'. These classes provide reference counting and object deletion for derived classes.
'bdlfi': This package is DEPRECATED. Concrete reference-counted function objects, held and managed by a 'bdlf' object; classes in 'bdlfi' implement behavior declared in 'bdlfr'.
'bdlfu': This package is DEPRECATED. Factory method utilities to initialize 'bdlf' functors by creating a reference-counted function object (from 'bdlfi') and assigning it to a 'bdlf' functor. Subsequent invocations of the 'bdlf' functor will be delegated to the 'bdlfi' object.
The 'bdlf', 'bdlfr', 'bdlfi', and 'bdlfu' packages provide identical functionality to the corresponding packages in the 'bce' package group: 'bcef', 'bcefr', 'bcefi', and 'bcefu', respectively. The difference between the two sets of packages is that the 'bce' packages provide thread safety, whereas the 'bdl' packages do not. In particular, package 'bcefr' implements reference counting via atomic operators provided in package bces_atomicutil .
Function pointers should be familiar to both C and C++ programmers. A function pointer is a pointer to a C-style function. In C++, the function pointer is declared to point to a function with a specific return type and a specific number of arguments of specific types. For instance,
declares a function pointer 'fp' that points to a function returning 'void', and taking exactly two arguments, an 'int' and a const pointer to 'char'.
Assume, then, that we have one or more functions declared with the matching signature:
After assigning a value to the function pointer, we can call the corresponding function:
Functors are objects that behave syntactically and semantically like functions. Functors implement a function-call operator ('operator()') whose signature characterizes the particular functor's type. In this example, for instance, we could define a functor like this:
Functors provide some interesting advantages over simple function pointers. Unlike a function pointer, a functor may be created with one or more arbitrary objects (sometimes called "user data") to be passed (typically as trailing arguments) to the underlying function. By "pre-binding" particular arguments to the underlying function, a functor can reduce the number of arguments a caller must supply when the functor is invoked. In this way, function objects can be used to coerce functions with extra arguments of arbitrary type into a standard calling signature. Even (non-'static') member functions can be encapsulated within functors and treated uniformly, simply by supplying the appropriate object at construction. Both the object pointer and a member function pointer are stored in the functor; the given object pointer is dereferenced when the functor is invoked.
Individual components are "numbered" to indicate the number of arguments (between 0 and 6) that a particular functor accepts. Each component defines a templated class, whose template parameters correspond in number to the functor arguments. When instantiated, the template argument types match the argument types used in invoking the functor. The component name is bdlf_vfunc followed by 0 to 6; the letter 'v' indicates that the corresponding functor will return 'void'. For the example above, we would use component bdlf_vfunc2 to create a functor object whose 'operator()' member function obeys the required signature.
Before a 'bdlf' functor is invoked as illustrated, the client is responsible for ensuring that it is valid; that is, it must contain a pointer to a function that will be called when the functor is invoked. The function to be called is external to the functor, and supplied by the client. This "external function" may be a free function, a static class method, or a (non-'static') member function for some class. The client, then, needs a canonical mechanism, regardless of the function type, to create a reference to that function, and to bind the reference to the 'bdlf' functor.
The most common way to accomplish this is to use a "factory method" defined in the 'bdlfu' package. The 'bdlfu' package contains 10 components which provide these factory methods. Parallel to 'bdlf', these components are "numbered" to support functors with different numbers of arguments. The factory methods are also templatized to support varying argument types.
Each 'bdlfu' component contains 4 different types of factory methods, varying by the type of function pointer required. For example, bdlfu_vfunc2 defines the following factory method families:
These factory methods (except for 'makeNull') are templatized to support different return and argument types; in addition, each factory method family consists of a set of overloaded functions that support varying numbers of arguments on the underlying functions (member or free). All classes returned by functions in component bdlfu_vfunc2 are defined in component bdlfi_vfunc2 . See package documentation for the {'bdlfu'} package for details.
For the simple example above, we would use component 'bdlfu_Vfunc2' as follows to initialize the 'bdlf_Vfunc2' object:
The 'bdlfu_Vfunc2::makeF' factory method in the above example expects two arguments. The first is a pointer to the functor object itself, and the second is a pointer to a free function with a signature matching the functor object, that is, expecting args of 'int' and 'const char*', and returning 'void'.
The 'makeF()' functions create instances of classes from the 'bdlfi' component; the specific classes are determined by the number and types of arguments on the 'bdlf' functor and the free function. Similarly, class member functions can be used to initialize a 'bdlf' functor by invoking a 'bdlfu_Vfunc2::makeM()' or 'bdlfu_Vfunc2::makeC()' function.
The 'bdlfu' and 'bdlfi' packages will also allow clients to match a functor to a free or member function in situations where the function requires more arguments than the functor invocation can support. See the documentation for package {'bdlfu'} for details describing how to do this; see package documentation for {'bdlfi'} for implementation details.
Note that because each required argument type 'T' is passed by ('const' 'T&'), a compiler will generate an error if the user declares a callback function taking an argument via a non-'const' reference parameter. This is intended to support and enforce the rule that all modifiable arguments are passed using pointers and not using references. For rare situations where clients want to use a callback function supplied by a third party, and that callback function uses non-'const' references, a wrapper function must be declared that converts non-'const' references to 'const' references or pointers as appropriate.
The functors in the 'bdlf' package are implemented using the envelope-letter idiom. The classes in 'bdlf' are *"envelopes"*; classes in 'bdlfr' and 'bdlfi' are *"letters"*. The use of the envelope-letter idiom allows clients to wrap arbitrary functions, whose signatures may differ from the function signatures in 'bdlf', with functors that support standard, well-known signatures.
Each 'bdlf_Vfunc*' functor *(envelope)* holds a pointer to an object *(letter)* from the corresponding 'bdlfi_vfunc*' component. The 'bdlf' envelope forwards or delegates client requests to its letter object: in particular, the 'operator()' member function used by clients to invoke the functor is delegated to the letter object for execution.
The letter object is polymorphic: all letter classes in a given 'bdlfi' component are derived from a common protocol or abstract base class (defined in 'bdlfr'). The 'bdlf' envelope uses the abstract 'bdlfr' protocol to delegate invocation requests. Different polymorphic letter types are used to support different external function types and signatures, and the single envelope type is used to provide a consistent function signature to clients.
Since polymorphism is used to support different bindings to external member or free functions, clients must be able to generate a specific type of letter object for a given envelope, based upon the type and signature of the specific external function. This may be done directly through public interfaces of the corresponding 'bdlfi' component, but in most cases it is simpler to use a suite of template functions provided by the corresponding 'bdlfu' package. Different template functions are provided to match (1) the desired function-call-operator signature and (2) the supplied user data. Because the C++ compiler will deduce template parameters from argument types, clients need not specify them. See the {'bdlfu'} package for more information on populating 'bdlf' functors with specific letter objects.
See also J.O. Coplien, "Advanced Programming Styles and Idioms", Sections 5.5-5.6, for a complete discussion of the envelope/letter idiom.
The bdlf_bind component provides a mechanism for creating function objects different from the 'bdlf_vfunc*' components. bdlf_bind is used to adapt an invocable object so that it conforms to a different interface and can be invoked with fewer arguments and/or with the arguments in a different order. It does this by binding some or all of the original invocable's arguments to fixed values that are known at construction time and binding the remaining arguments to placeholders for arguments that are provided at invocation time. A binder can be used as an argument for any template function that requires an invocable with compatible parameters.
The bdlf_bind component defines binder objects and factory methods that can bind any arguments into a functor in any order, enabling some of the arguments of the invocable to be specified at the construction time, and leaving (via place-holders, defined in component bdlf_placeholder ) some others to be specified (later) at invocation time. A binder is created by one of the three factory method families defined in component bdlf_bind :
Binders where the signature of the invocable can be deduced are called explicit. Not all binders are explicit. For non-explicit binders, the signature of a 'bdlf_Bind' object is not embedded in the type, and several overloads can be invoked through the same binder. Unlike 'bdlf_Vfunc*' objects, different binders accepting the same signature may have different types depending on the types of the bound arguments if they are non-explicit. The return type (not necessarily void) is also part of the signature. Thus, bdlf_bind provides compile-time polymorphism but not run-time polymorphism.
A broad discussion of binding, along with limitations of the 'usage examples, can be found in the component documentation of component bdlf_bind .
In addition to signature-specific objects and binders, the 'bdlf' package provides a run-time polymorphic 'bdlf_Function' template. A 'bdlf_Function' instantiation is capable of holding any invocable object of compatible prototype, including a function pointer, 'bdlf_MemFn' wrapper, any concrete implementation of the 'bdlfr_Vfunc*'protocols, any instances of the 'bdlf_Vfunc' family, and any other function object having an 'operator()' method including an instance of a 'bdlf_Bind' instantiation.
Like a binder, a 'bdlf_Function' instantiation can have a return value (or return 'void'). An instance does not store bound arguments directly. It accepts all its arguments at invocation time only, although using polymorphic representations and the 'bdlfu_Vfunc*::make*' factory methods, it is possible to bind the last arguments of an invocable and wrap this into a 'bdlf_Function' object which will then accept only the remaining arguments upon invocation. As mentioned above, it is also possible to assign a 'bdlf_Bind' object to a 'bdlf_Function' instance to bind and route arguments to the invocable in an arbitrary manner.
Unlike a binder, the signature of a 'bdlf_Function' object is part of the type and invocation arguments are cast to their corresponding type before they are passed to the invocable. Most importantly, the same functor may be assigned from various types (pointer to function, 'bdlf_MemFn' wrapper, user-defined function objects, or even 'bdlf_Bind' binders).
'bdlf_Function' objects (functors) and 'bdlf_Bind' objects (binders) are both mechanisms for binding arguments and calling an invocable. They differ in their purpose, though. In the last section, we detailed the main features of the bdlf_function and bdlf_bind components. In this section we point out the differences, and how they interact.
The main difference is compile-time (for bdlf_bind ) vs. run-time (for bdlf_function ) polymorphism. An instantiation of 'bdlf_Function' is a type of functor that can hold any invocable object with compatible prototype. Regardless of what type of (compatible) invocable is used to create the 'bdlf_Function', the type of the resulting functor does not change. Thus, 'bdlf_Function' has run-time polymorphism and is ideally suited for callbacks because it adapts the invocable to the callback interface. Run-time polymorphism is extremely useful for defining callback types and storing callbacks, since passing a new type of callback does not require re-compilation. Compile-time polymorphism provides the ability to invoke several overloads through the same functor (binder) and to bind and route arguments to the invocable in an arbitrary manner.
In cases where both binders and functors could be used, 'bdlf_Function' has certain advantages, not least of which is simplicity (leading to shorter compilation times), and the memory optimization documented below, whereby small objects are constructed in-place but larger objects are dynamically allocated. Note that binders are always constructed in-place and can have rather large footprints. Functors (including instances of 'bdlfr_Vfunc*' derived types) can implement various strategies like sharing and copy-on-write to reduce the cost of copying and the footprint at the same time.
'bdlf'-style functors provide a type-neutral, exception-safe (and potentially, but not yet, thread-safe) properly managed alternative to the traditional callback paradigm (where "client data" is placed in a single structure whose address is cast to type 'void *'). The following example illustrates how functor callbacks can be added to a graphical object.
Let's suppose that the implementor of the 'MyGuiButton' class allows the client to specify a callback function that should be called whenever a button is pressed. Furthermore, the implementor agrees to provide to the callback function two arguments at its invocation: a modifiable object of type 'MyGuiContext', and a non-modifiable object of type 'MyGuiLocation'.
Here is the implementation of the 'MyGuiButton' class:
The 'context' and 'location' arguments are mandatory for every function called at the press of the button (the number '2' in the name of 'bdlf_Vfunc2' class specifies the number of required arguments). However, we also allow the user of 'MyGuiButton' class to invoke the function with a number of additional parameters, as in the 'buttonpressFunction' function presented below.
The 'bdlf_Vfunc2<MyGuiContext, MyGuiLocation>' class used in 'MyGuiButton' class has a private member of an abstract class bdlfr_vfunc2 , whose 'execute' method is called when the functor is being called. The 'execute' method of the bdlfr_vfunc2 class, the functor invocation operator of the 'bdlf_Vfunc2' class, and the 'pressButton' method of 'MyGuiButton' class have two parameters. The 'buttonpressFunction' callback function, however, has to be called with three arguments. To achieve the proper calling signature the user implements a concrete class derived from bdlfr_vfunc2 as follows:
Note that the required arguments are passed in at the functor invocation, and optional arguments are passed in at the functor initialization. By BDE convention, a function signature consists of output parameters, and then input parameters. Since here we have two parameter lists (one passed in by the caller, and one passed in by the callee) we can follow this convention within each list only. We choose to pass in first the required arguments (passed by the caller) and then the optional arguments (supplied by the callee). This order allows a callee to add parameters easily at the end of the function parameter list. The number of required parameters is a part of the 'MyGuiButton' interface, and hence will never change.
The following code shows how we:
The code is as follows: