I l@ve RuBoard |
![]() ![]() |
5.12 Real-World Issues I: The Cost of Forwarding FunctionsThe Functor class template is conceptually finished. Now we concentrate on optimizing it so that it works as efficiently as possible.[6]
Let's focus on one of Functor's operator() overloads, which forwards the call to the smart pointer. // inside Functor<R, TList> R operator()(Parm1 p1, Parm2 p2) { return (*spImpl_)(p1, p2); } Each time you call operator(), an unnecessary copy is performed for each argument. If Parm1 and Parm2 are expensive to copy, you might have a performance problem. Oddly enough, even if Functor's operator() is inline, the compiler is not allowed to optimize the extra copies away. Item 46 in Sutter (2000) describes this late language change, made just before standardization. What's called eliding of copy construction was banned for forwarding functions. The only situation in which a compiler can elide a copy constructor is for the returned value, which cannot be optimized by hand. Are references an easy fix for our problem? Let's try this: // inside Functor<R, TList> R operator()(Parm1& p1, Parm2& p2) { return (*spImpl_)(p1, p2); } All looks fine, and it might actually work, until you try something like this: void testFunction(std::string&, int); Functor<void, TYPELIST_2(std::string&, int)> cmd(testFunction); ... string s; cmd(s, 5); //error! The compiler will choke on the last line, uttering something like "References to references are not allowed." (Actually, the message may be slightly more cryptic.) The fact is, such an instantiation would render Parm1 as a reference to std::string, and consequently p1 would be a reference to a reference to std::string. References to references are illegal.[7]
Fortunately, Chapter 2 provides a tool that addresses exactly this kind of problem. Chapter 2 features a class template TypeTraits<T> that defines a bunch of types related to the type T. Examples of such related types are the non-const type (if T is a const type), the pointed-to type (if T is a pointer), and many others. The type that can be safely and efficiently passed as a parameter to a function is ParameterType. The following table illustrates the link between the type you pass to TypeTraits and the inner type definition ParameterType. Consider U to be a plain type, such as a class or a primitive type.
If you substitute the types in the right column for the arguments in the forwarding function, it will work correctly for any case—and without any copy overhead: // Inside Functor<R, TList> R operator()( typename TypeTraits<Parm1>::ParameterType p1, typename TypeTraits<Parm2>::ParameterType p2) { return (*spImpl_)(p1, p2); } What's even nicer is that references work great in conjunction with inline functions. The optimizer generates optimal code easier because all it has to do is short-circuit the references. ![]() |
I l@ve RuBoard |
![]() ![]() |