1

I have an expensive function defined in a base class, which depends on low level information from its derived classes:

class BaseClass{
 ...
 // Defined in derived class
 virtual int low_level(int)=0;

 // Expensive function depending on the pure virtual function
 void myExpensiveFunction(){
   for(...){
     for(...){
       for(...){
         ... = low_level(...);
         ...
       }
     }
   }
 }
};

class DerivedClass : public BaseClass{
   // A very cheap operation that can be inlined:
   inline virtual int low_level(int i){
     return a[i];
   }

   // Calling the base class function
   void test(){
     myExpensiveFunction();
   }
};

If I understand things correctly, the fact that the low-level function is virtual prevents it from being inlined in the code above. Now, I was thinking about a way to get around this and thought of the following solution, where I pass a pointer to the derived class member function as a template parameter:

class BaseClass{
 ...
 // The function is now templated by the derived function:
 template<typename D, int (D::*low_level)(int)>
 void myExpensiveFunction(){
   for(...){
     for(...){
       for(...){
         ... = static_cast<D*>(this)->low_level(...);
         ...
       }
     }
   }
 }
};

class DerivedClass : public BaseClass{
   // A very cheap operation that can be inlined:
   inline int low_level(int i){
     return a[i];
   }

   // Calling the base class function
   void test(){
     myExpensiveFunction<DerivedClass,&DerivedClass::low_level>();
   }
};

Does this strategy make sense? I imagine that the low level operation will be inlined when the expensive base class function is expanded in the derived class.

I tested implementing it and it compiles and works, but I haven't seen any noticeable differences in performance.

Kind regards, Joel

Joel
  • 1,295
  • 15
  • 30
  • *Does this strategy make sense?* No – BЈовић Feb 11 '12 at 12:24
  • *I imagine that the low level operation will be inlined when the expensive base class function is expanded in the derived class.* Did you check the assembly? – jpalecek Feb 11 '12 at 12:30
  • Is it an option to obtain a reference to `a` in the expensive function? – Kerrek SB Feb 11 '12 at 12:44
  • @jpalecek: I didn't check the assembly, no. I could try to do that (though I don't have any experience in looking at assembly code). – Joel Feb 11 '12 at 13:19
  • @KerrekSB: It is not an option to obtain a reference to `a`, since each derived class will implement the low-level function differently. In the example above, it happened to be a vector, in another derived class it would be something else. – Joel Feb 11 '12 at 13:22
  • I think this is similar to [a previous question of mine](http://stackoverflow.com/q/7451442/596781). Apparently in Objective-C++ you can save the dynamic lookup outside the loop once and for all... – Kerrek SB Feb 11 '12 at 13:26

2 Answers2

6

Passing the function you want to call using a pointer to member to a base class doesn't really improve over using a virtual function. In fact, I would expect it to make the situation worse. An alternative approach is to use a function object with an inline function call operator and call this. The "normal" approach is to kind of invert the class hierarchy and use the Curiously Recurring Template Pattern: the idea is to create a template which will derive from its template argument. The template argument is expected to provide the customization points, e.g. the function low_level.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I didn't know about the Curiously recurring template pattern idiom. It is definitely more elegant, so I will use that instead. Though I still don't understand why my solution with passing member function templates does not work. I mean, the compiler has all the information it needs to inline the function. – Joel Feb 11 '12 at 13:33
  • I don't say that it doesn't work. It is just that I in the past I tried similar things and measured the effect and they tended not to work. To find out what works best, you clearly want to measure the performance. In addition, you might want to look at the generated as was suggested elsewhere: you don't need to understand the assembler but you can normally spot if things are inlined or not. ... and you would look at this probably in a simplified setting first. – Dietmar Kühl Feb 11 '12 at 17:30
  • Alright. Anyway, the "curiously recurring template pattern" idiom works fine so I'll stick to that. Thanks a lot!! – Joel Feb 11 '12 at 18:00
4

Depending on the situation, you could also try to avoid inheritance altogether and do something like this instead:

template<typename LL>
class HighLevel {
  LL lowLevel;

  public:

    HighLevel(LL const &ll) : lowLevel(ll) { }

    void myExpensiveFunction() {
      for(...) {
        for(...) {
          for(...) {
            ... = lowLevel.low_level(...);
            ...
          }
        }
      }
    }
};

class LowLevel {
  public:
    inline int low_level(int i) { // note: not virtual
      return a[i];
    }
};

Used like:

HighLevel<LowLevel> hl;
hl.myExpensiveFunction();

If you don't want different types of HighLevel<...> objects floating around, you could derive all those from an abstract, non-template class HighLevelBase which exposes a virtual void myExpensiveFunction() = 0 that gets implemented in the template.

Whether or not this makes sense for your situation, I cannot tell without more information, but I find that C++ often offers better tools than inheritance to solve particular problems.

Thomas
  • 174,939
  • 50
  • 355
  • 478