Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Aspect-Oriented Programming & C++


August, 2004: Aspect-Oriented Programming & C++

A powerful approach comes to C++

Christopher is a freelance computer programmer and developer of Heron, a modern, general-purpose, open-source language inspired by C++, Pascal, and Java. He can be contacted at http://www.heron-language.com/.


Heron


Aspect-oriented programming (AOP) is a technique for separating and isolating crosscutting concerns into modular components called "aspects." A crosscutting concern is a behavior that cuts across the boundaries of assigned responsibility for a given modular element. Examples of crosscutting concerns include process synchronization, location control, execution timing constraints, persistence, and failure recovery. There is also a wide range of algorithms and design patterns that are more naturally expressible using AOP.

For the most part, discussions of AOP have focused on Java; see, for example, "Lightweight Aspect-Oriented Programming," by Michael Yuan and Norman Richards (DDJ, August 2003) and "Aspect-Oriented Programming & AspectJ," by William Grosso (DDJ, August 2002). The main reason for this is that the majority of advancement of AOP has been done through AspectJ, a Java language extension and set of Java-based tools designed for AOP (http://www.eclipse.org/aspectj/). However, C++ programmers can also benefit from AOP because it lets you better express designs with crosscutting concerns than is possible with other, more well-known techniques, such as object-oriented design or generic programming.

Until recently, the application of AOP technology has been constrained to type-modifying systems. Type-modifying AOP systems modify an existing type—in most languages, this is only achievable through the use of a language preprocessor. Furthermore, most of the initial application of AOP technology has been done within the AspectJ project. This is changing with the advent of AOP preprocessors, such as AspectC++, an AOP language extension for C++ (http://www.aspectc.org/).

In this article, I examine the concepts behind AOP and present techniques that demonstrate how you can implement AOP via macros. For example, Listing One is pseudocode that illustrates crosscutting concerns by introducing them into a hypothetical class, FuBar, one by one.

FuBar is straightforward—it does Fu and it does Bar. But say you want to debug at some point by dumping the state of FuBar before and after each function call; see Listing Two. Once the error is found, you leave the code in for another day, with the DumpState calls conditionally compiled based on a debug switch. Now say that your project manager informs you that there is a new user-defined setting that decides whether the function Bar should actually do Bar, or whether it should do nothing. So you modify the code as in Listing Three.

Now imagine that CFuBar somehow finds its way into a real-time simulation. In this scenario, Fu and Bar turn out to be bottlenecks, so you simply increase the executing thread priority as in Listing Four. One day, a tester finds a problem with your code. It turns out that the internal state goes invalid at some point, but there are thousands of calls to Fu and Bar in the software and, as such, state dumping is not appropriate. You could then identify and fix the bug by applying a class invariant check for each of the calls; see Listing Five. Then one day, the application goes multithreaded and you need to protect Fu and Bar from mutual entry. Consequently, you add a lock/unlock mechanism to maintain thread safety in FuBar (see Listing Six).

Despite its niveté, this example demonstrates interleaved concerns and should be quite recognizable. The main concern is the class FuBar, whose responsibility is to do Fu and Bar (whatever they may be), while the interleaved concerns are those of debugging, real-time constraints, thread synchronicity, and the algorithmic concern of a user-defined flag to prevent you from doing "bar."

The obvious problem is that, while FuBar may work fine, it is now much more complex than before. As a result, it is too specialized to be reusable, and is becoming increasingly difficult to maintain and update. In an ideal world, we would be able to express the various concerns in separate modules or classes. This is where aspect-oriented programming comes in.

The goal of AOP is to leave the original FuBar as untouched as possible, and to separate the concerns into new modules (or in our case, classes) that themselves know as little about FuBar as possible. These separated concerns are aspects, which work by providing advice on what to do at specific points of intersection in the target class. The points of intersection are called "joinpoints," and a set of joinpoints is called a "pointcut." The advice of an aspect can take the form of entirely new behavior, or a simple observation of execution.

What C++ lacks is an operation that lets you define a new class from a target class by declaring which aspects operate on it, and at which pointcuts. One way to define new operations is through a macro, so consider this as yet undefined macro:

CROSSCUT(TARGET, POINT_CUT, ASPECT)

This macro defines a new type by introducing a reflective wrapper around the TARGET class for the set of functions defined in the POINT_CUT class. This wrapper would then implement the advice found in the ASPECT.

You can imagine using this imaginary CROSSCUT macro to create a new FuBar with built-in state-dumping functionality:

typedef CROSSCUT(FuBar, FuBarPointCut,
StateDumpingAspect) DumpStateFuBar;

This statement can be read as: Define a new type named "DumpStateFuBar" by taking an existing type, FuBar, and applying the aspect StateDumpingAspect at the functions listed in FuBarPointCut. What's now missing is the definition of the pointcut and the aspect. A pointcut is a list of functions so, conceptually, the FuBarPointCut could be thought of as:

pointcut FuBarPointCut {
Fu;
Bar;
}

meaning that FuBarPointCut represents the set of functions named Fu or Bar.

The aspect introduces crosscutting behavior by providing implementations of advice functions that are invoked at specific locations around the invocation of the joinpoints.

Here are the advice functions that I will use from here on:

  • void OnBefore(). Called before the joinpoint function would be called.
  • void OnAfter(). The last function called if the execution of joinpoint does not throw an exception.
  • bool OnProceedQuery(). If this function evaluates to False, then the entire joinpoint function is not called.
  • void OnFinally(). Called after execution of the joinpoint regardless of whether a function is called.
  • void OnExcept(). Called just after OnFinally if an error occurs. For users to handle the exception, they must rethrow it and catch it from within their implementation of OnExcept().

A logical implementation of an aspect uses a base class with virtual functions. You could then define the base class in Listing Seven for the next part of this example. So, for the DumpStateFuBar example, the state dumping aspect would look like Listing Eight.

What I have done is declare the crosscutting concern in a separate class called StateDumpingAspect and used the crosscut macro to declare a new type, which intersects the concern with the original class at a specified set of locations (the pointcut). This is the concept of isolation and separation of concerns that forms the basis of AOP. All of this is fine and dandy but what gets really interesting is to combine the aspects. For that, you would define a new type such as Listing Nine. You may notice that in Listing Ten, which presents pseudocode implementations of the various new aspects, there is a new pointcut named "BarPointCut," which represents the set of functions named "Bar." This is because I want to apply the BarPreventingAspect to the function Bar and not Fu. This is demonstrative of the power of pointcuts in that an aspect can be written generally, and applied specifically through the use of a pointcut.

What I have done here is use the crosscut macro to pile aspects up on top of FuBar hierarchically, isolating and separating a huge amount of complexity into reusable aspects. This is a much more flexible and powerful design than the more naive approach we exemplified earlier with the degenerate FuBar.

Implementing Crosscut Macros

I now turn to an example that uses a simple implementation in C++ of a crosscut macro along with macros for declaring joinpoints. The files aspect.hpp and aspect.cpp (available electronically; see "Resource Center," page 5) look similar to the previous pseudocode example. However, here I take a single class Point, and generate from it a new class Point_T, which introduces two crosscutting concerns—a LoggingAspect and PreventiveAspect.

The aspects in this example are user-defined classes that inherit from BaseAspect and provide their own implementations for the OnXXX functions. The main difference between the aspects in this example and the previous one is that I have now introduced a parameter for each function of type FxnDesc—a function descriptor containing important information about the joinpoint that gets passed to the aspect when a joinpoint is invoked. FxnDesc contains the function name, the class name, parameter names, and names of the parameter types as strings.

The pointcuts here are defined using the macros DEF_POINTCUT(CLASS_NAME) and END_POINTCUT. It is useful to know that the pointcut macros generate a new parameterized class with the name of CLASS_NAME, which inherits from two bases that are passed as parameters. This is in fact what the CROSSCUT macro uses to define a new class.

Within the pointcut definition is a list of joinpoint definitions. I have several different macros that depend on a specific kind of function signature where the joinpoint occurs. For instance, SET_PROCJOINPOINT2 is used when the joinpoint has a void return type and accepts two parameters.

Each of the joinpoint macros generate a function that calls the member functions of an aspect with a properly initialized FxnDesc struct, and then calls the joinpoint itself (assuming OnProceedQuery returns True).

The crosscut macro uses the generated pointcut class as a new class that inherits from both the target and from the aspect. Each of the joinpoint functions is overridden and given a new implementation that calls the inherited advice from the aspect around the call to the original implementation of the target class.

Why Is This Crosscut Macro So Special?

Showing a few examples does not fully do the crosscutting macro justice. With practice and a little imagination, more and more opportunities to use crosscutting to separate concerns become apparent. It is important to note that crosscutting is about reducing complexity, the advantage of which only starts to become apparent in real nontrivial applications of the technique. This is because complexity scales so poorly and is one of the major obstacles to large-scale software development.

Future Directions

There is clearly much work left to be done with the code provided here to make it more robust and efficient. For instance, there may be better implementations of the outlined AOP techniques that uses templates. Another possible feature that would make the crosscutting macro much more powerful would be for it to allow observation and modification of the argument values by the advice functions. Hopefully, others will be motivated to refine the work presented here to make it more robust and fully usable in industrial-strength C++ libraries.

Unfortunately, the crosscut macro can only do so much within the constraints of C++. For instance, you can't use it to simultaneously crosscut multiple classes. C++ also lacks a facility to express pointcuts in a more general manner. These are two of the most glaring omissions compared to tools like AspectC++ and AspectJ but does not change the fact that the crosscutting macro, even in its current stage of relative infancy, can be a useful tool in the C++ programmer's arsenal.

Acknowledgments

Thanks to Kris Unger, Muthana Kubba, Daniel Zimmerman, and Matthew Wilson for their proofreading and input on this article, and Cristina Lopes and Walter Hursch whose work showed me the importance of AOP.

DDJ



Listing One

class FuBar {
  Fu() {
    // do fu
  }
  Bar() {
    // do bar
  }
}
Back to article


Listing Two
class FuBar {
  Fu() {
    DumpState();
    // do fu
    DumpState();
  }
  Bar() {
    DumpState();
    // do bar
    DumpState();
  }
}
Back to article


Listing Three
class FuBar {
  Fu() {
    DumpState();
    // do fu
    DumpState();
  }
  Bar() {
    DumpState();
    if (!prevent_bar_flag) {
      // do bar
    }
    DumpState();
  }
}
Back to article


Listing Four
class FuBar {
  Fu() {
    DumpState();
    SetThreadPriorityCritical();
    // do fu
    RestoreThreadPriority();
    DumpState();
  }
  Bar() {
    DumpState();
    SetThreadPriorityCritical();
    if (!prevent_bar_flag) {
      // do bar
    }
    RestoreThreadPriority();
    DumpState();
  }
}
Back to article


Listing Five
class FuBar {
  Fu() {
    DumpState();
    SetThreadPriorityCritical();
    // do fu
    RestoreThreadPriority();
    DumpState();
  }
  Bar() {
    DumpState();
    SetThreadPriorityCritical();
    if (!prevent_bar_flag) {
      // do bar
    }
    RestoreThreadPriority();
    DumpState();
  }
}
Back to article


Listing Six
class FuBar {
    Fu() {
      Lock();
      TestInvariant();
      DumpState();
      SetThreadPriorityCritical();
      // do fu
      RestoreThreadPriority();
      DumpState();
      TestInvariant();
      Unlock();
    }
    Bar() {
      Lock();
      TestInvariant();
      DumpState();
      SetThreadPriorityCritical();
      if (!prevent_bar_flag) {
        // do bar
      }
      RestoreThreadPriority();
      DumpState();
      TestInvariant();
      Unlock();
    }
  }
Back to article


Listing Seven
class BaseAspect {
  virtual void OnBefore() { /* do nothing */ };
  virtual void OnAfter() { /* do nothing */ };
  virtual bool OnProceedQuery() { return true; };
  virtual void OnException() { throw; };
  virtual void OnFinally() { /* do nothing */ };
};
Back to article


Listing Eight
class StateDumpingAspect : public BaseAspect {
  virtual void OnBefore() {
    DumpState();
  }
  virtual void OnAfter() {
    DumpState();
  }
};
Back to article


Listing Nine
typedef
  CROSSCUT(
    CROSSCUT(
      CROSSCUT(
        CROSSCUT(
          CROSSCUT(FuBar, FuBarPointCut, StateDumpingAspect),
        BarPointCut, BarPreventingAspect),
      FuBarPointCut, RealTimeAspect),
    FuBarPointCut, InvariantAspect),
  FuBarPointCut, SynchronizeAspect)
NewFuBar;
Back to article


Listing Ten
class BarPreventingAspect : public BaseAspect {
  virtual bool OnProceedQuery() {
    return (!global_prevent_bar_flag);
  }
};
class RealTimeAspect : public BaseAspect {
  virtual void OnBefore() {
    SetThreadPriorityCritical();
  }
  virtual void OnAfter() {
    RestoreThreadPriority();
  }
};
class InvariantAspect : public BaseAspect {
  virtual void OnBefore() {
    TestInvariant();
  }
  virtual void OnAfter() {
    TestInvariant();
  }
};
class SynchronizeAspect : public BaseAspect {
  virtual void OnBefore()
    Lock();
  }
  virtual void OnAfter()
    Unlock();
  }
};
  
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.