Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Aspect Oriented Programming

Last updated Jan 1, 2003.

There's a new game in town, called Aspect Oriented Programming (AOP). You can't participate in it -- not yet at least -- and perhaps it's just as well. Once you've grasped the rules of this game, you might question whether it's really worth the trouble. Curious? Here are the details.

AOP and ISO C++

In its present state, C++ doesn't support AOP directly, nor are there any plans to incorporate AOP facilities in C++0x (at least if judged by the draft proposals submitted to the Evolution Working Group). Although there have been a few attempts to ship proprietary AOP variants of C++ that rely on nonstandard extensions, the main interest in AOP usually comes from other programming languages, notably Java. At present, there are several Java-based AOP IDEs that support more or less the same concepts, although each of them implements AOP constructs differently, sometimes extending the core language with new nonstandard keywords and syntactic constructs.

Another Silver Bullet?

OOP purportedly simplifies and automates large parts of manual coding. In large-scale systems, this claim doesn't always hold true. Ideally, when a new requirement triggers code changes, it's localized to a single member function or a class. This is the optimistic scenario.

Sometimes, however, such a new requirement may involve a wholesale code rewriting. Take for example a requirement to log every invocation of every function that takes a void * argument. This isn't such a capricious requirement as it may first appear: think of testing a library's 64-bit compliance for example. Such a requirement is called a cross-cutting concern -- it cannot be isolated or encapsulated in one or two isolated code spots; but rather involves changes in many places across the system. Even if the logging operations are implemented as a single coherent class, it isn't very helpful, because you still need to add the logging functionality to every piece of code that meets this criterion. From my experience (and yours as well, I'll wager), this is exactly the kind of last-minute requirement that bosses and hysterical customers like to throw on you three days before the project's finishing line. Quite a nightmare, isn't it?

Join-points, Point-cuts, and Advice

AOP tackles this problem by means of join-points and a matching point-cut. A join point is a well-defined point in the code at which our concern crosscuts the application. In plain English, a join-point is one of many spots in the code that are affected by the new requirement. Typically, there are many join points for each concern; otherwise, we could fix the code manually.

AOP IDEs enable you to define a single point-cut, or a rule, that characterizes all of the join-points. For example, here's how you define (in a hypothetic variant of C++) a point-cut called pc1 that affects: "every SQLQuery member function that begins with set, ends with Table and which takes an argument of type const std::string &":

pointcut pc1: call( // associated event is a function call
 int SQLQuery::set*Table ()) //define applicable functions
  && args(const std::string&); //filter by argument's type

AOP IDEs offer a rich set of syntactic constructs allowing you to define various criteria. In the following example, I numbered the join-points that that are included in this point-cut:

class SQLQuery
{
//..
public:
int getIndexTable(const std::string &tablename) const; 
int setDataTable(const std::string &tablename); //1
int setViewTable(const std::string &tablename); //2
int setTextTable(const std::string &tablename); //3
int resetViewTable(const std::string &tablename); 
int setLookupTable(const std::string &tablename); //4
int setTextTable(int idx); 
int setTableSize (long n_items); 
};

Even with such a small number of member functions, it's easy for a human programmer to mistakenly skip a join-point or include a false join-point. Try to imagine a more realistic scenario: making the Standard Library thread-safe, for example.

An advice defines the actual change that applies to a join-point. I'm not particularly keen about this term because it implies that the advice is optional, which it isn't. Perhaps the closest analogy would be a C++ exception handler: whenever a certain join-point is met, the implementation automatically invokes the matching advice (handler), just as an exception handler is invoked automatically when a certain runtime condition is met.

Let's summarize what we've learned thus far. A cross-cut concern is a requirement that affects many specific places in our code. These places are called join-points. A point-cut is a formal definition (often using special syntax and regular expressions) of a cross-cut concern. Finally, an advice is the operation that takes place when control reaches a join-point. There are three types of advice: before, after, and around. They execute before the join point, after the join point, and instead of the join point, respectively. It's possible to combine two or more advices per point-cut, and vice versa. An aspect is a type similar to a class that defines point-cuts and their matching advice(s).

Back to our example. We can define a before advice that writes to a log just before any of the following member functions is called:

int SQLQuery::setDataTable(const std::string &tablename); //1
int SQLQuery::setViewTable(const std::string &tablename); //2
int SQLQuery::setTextTable(const std::string &tablename); //3
int SQLQuery::setLookupTable(const std::string &tablename); //4

Defining an after advice for the same point-cut causes the logging operation to take place after each function returns. An around advice is particularly useful for debugging and profiling purposes, say when you want to check how many times certain member functions that update a database are called without actually accessing the database.

Around the Bend?

Thus far, AOP seems like a magic spell. However, it has a few disturbing aspects [pun intended] that have to be considered.

  • Gaps between source file code and runtime behavior. When your IDE compiles an aspect, it doesn't re-edit your source files to reflect the new behavior. Rather, the new aspect is woven into intermediary files that you normally don't see. Thus, if all member functions of a certain project have an advice associated with them, the sources are actually very misleading. Java AOP IDEs weave the advice into the byte-code so the only way to see what's really happening is decompiling it. For a C++ programmer, this weaving is very reminiscent of macro magic -- a feature we've learned to abhor, with reason.
  • Testing. Because the changes entailed by an aspect aren't localized to a specific place, every slight modification of a point-cut or its matching advice could affect a varying number of join-points, possibly incurring time-consuming testing and debugging. Think, for example, about changing pc1 to:
    pointcut pc1: call( //"slightly" modified
     int SQLQuery::set*Table () const) //only const member functions
      && args(const std::string&); 
  • This "minor" change has in fact reduced the number of join-points to zero!
  • Exceptions. Suppose you want to modify almost every function with certain properties save one or two special cases. Alas, when the changes aren't reflected in the source files, it's easy to miss these exceptions. In C++, a similar phenomenon occurs with templates. However, there are mechanisms for overriding the default behavior of a primary template, namely partial specializations and explicit specializations.
  • Lack of standardization. This isn't really a problem because at some point, the industry will have to agree about the precise syntax and semantics of AOP-enabled IDEs. Presently, however, AOP constructs are very implementation-dependent.
  • Encouraging bad programming practices. Although it's too early to evaluate the merits of AOP, I suspect that it might lead to sloppy coding practices, whereby programmers are tempted to patch poorly-designed code by means of aspects instead of going back to the drawing board.

Epilog

For better or worse, C++ programmers don't have to worry about any of these for the time being. Let other languages be the guinea pigs of AOP; if and when this technology matures, it will reach C++ as well.