2

CLOS has a neat concept of :before, :after, and :around methods.

  • The :before methods are called before the primary method.
  • The :after methods are called after the primary method.
  • The :around methods are called around the :before+primary+:after sequence.

The :before, :after, and :around methods are chained rather than overridden. Suppose both a parent and child class define a foo method and :before foo method. The child's foo method overrides the parent's foo method, but both the child's and parent's :before foo methods are called before this overridden method is called.

Python decorators provide something similar to the CLOS :around methods. There is nothing like this in C++. It has to be hand-rolled:

class Child : public Parent {
    virtual void do_something (Elided arguments) {
        do_some_preliminary_stuff();
        Parent::do_something (arguments);
        do_some_followup_stuff();
    }
};

Downsides:

  • This is an anti-pattern to some people.
  • It requires me to be explicit in specifying the parent class.
  • It requires extenders of my classes to follow the same paradigm.
  • What if I need to call the grandparent because the parent doesn't override do_something, and what about multiple inheritance?
  • It doesn't quite capture the CLOS concept.

I found these concepts to be quite handy way back when I used Flavors (predecessor to CLOS). I've used the above workaround in a few places, and a few have challenged it as an anti-pattern. (Others have emulated it elsewhere, so the derision is not universal.)

The question: Is this considered an anti-pattern in C++, and are there better workarounds?

David Hammen
  • 32,454
  • 9
  • 60
  • 108

3 Answers3

2

You can get the basics of this quite nicely using (std/boost)::shared_ptr. For details see here : http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/sp_techniques.html#wrapper

Getting the inheritance behaviour you mention just requires the prefix/suffix functions to call the prefix/suffix functions in the parent class.

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • Interesting, particularly so since Stroustrup (briefly) entertained implementing the concept of CLOS :before and :after methods. – David Hammen Jul 08 '11 at 10:26
1

I'm not claiming this is equivalent or comparable to what other languages do, but I think the Non Virtual Interface idiom is applicable for your problem:

class parent {
public:
    void foo()
    {
        before_foo();
        do_foo();
        after_foo();
    }

protected:
    // you can make those pure virtual with an implentation, too
    virtual void before_foo() { ... }
    virtual void do_foo() { ... }
    virtual void after_foo() { ... }
};

class child: public parent {
protected: // or private
    void before_foo() { ... }
    void do_foo() { ... }
    // must provide a dummy after_foo that delegates to parent::after_foo
    // if it is pure virtual in the parent class
};

When calling p.foo(), the most derived before_foo, after_foo and do_foo are always called.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • I perhaps simplifies the example too much. I had a multiple levels of inheritance, each derived class doing stuff _around_ invoking the parent class. NVI is a possibility, though. Still kludgy as it still requires the derived classes to invoke the parent method. To get the before_foo and after_foo stuff right, the derived classes must call the parent before_ or after_ method. – David Hammen Jul 08 '11 at 10:10
  • I bumped both your answer and yi_H's. yi_H's answer comes closer to the flavor of emulating the CLOS wrapper methods, but I do like the idea of NVI for this. I'll keep both suggestions in mind when (if) I run across a need for that CLOS concept in the future. Thanks. – David Hammen Jul 08 '11 at 10:16
  • I used my design as an exemplar. My question was more of a curiosity: How best to emulate the CLOS :before, :after, and :around wrapper methods in C++? In CLOS, those wrapper methods chain, and do so in a particular way. – David Hammen Jul 08 '11 at 10:29
1

This is what I could do, but it's still a bit ugly.

Basically I put the actual work in a separate hook so you don't have call the pre/post hooks in the processing method. In the inheritance chain you have total control both on whether you want to add pre/post hooks and the ordering of the hook calls (call the parent's hook before or after the child's hook).

#include <iostream>
#define C(s) std::cout << s << std::endl;

class Parent {
    public:
    virtual void do_something(int arg) {
        do_some_pre_hook();
        do_some_hook(arg);
        do_some_post_hook();
    }
    virtual void do_some_pre_hook() {
        C("parent pre_hook");
    }
    virtual void do_some_post_hook() {
        C("parent post_hook");
    }
    virtual void do_some_hook(int arg) {
        //this is where you actually do the work
    }
};

class Child : public Parent {
    public:
    typedef Parent super;

    virtual void do_some_pre_hook() {
        super::do_some_pre_hook();
        C("Child pre_hook");
    }
    virtual void do_some_post_hook() {
        super::do_some_post_hook();
        C("Child post_hook");
    }
};

class GrandChild : public Child {
    public:
    typedef Child super;

    virtual void do_some_pre_hook() {
        super::do_some_pre_hook();
        C("GrandChild pre_hook");
    }
    virtual void do_some_post_hook() {
        super::do_some_post_hook();
        C("GrandChild post_hook");
    }
    virtual void do_some_hook(int arg) {
        //this is where you actually do the work
        C("GrandChild hook");
    }
};

int main() {
    GrandChild gc;
    gc.do_something(12);
}

Note: Ideally you would use an AOP c++ compiler or compiler extension for a task like that, but the last time I tried it it wasn't quite stable...

Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • Nice. That you made do_something virtual means a derived class can override it, and if it does so by invoking super::do_something, there's the CLOS :around as well. All in all a bit kludgy, but since the language doesn't support it, any implementation is going to be a bit kludgy. – David Hammen Jul 08 '11 at 10:19