In this article we will look at how to implement aspect-oriented programming
(AOP) in C++. AOP gives the ability to adapt existing classes or
methods. Functions often deal with many fundamentally separate things,
like locking, logging, validation or error handling, which are fundamentally
distinct concerns. AOP lets you extract those concerns and implement them
separately. Each concern can be implemented separately in an "aspect" and
applied to many different classes or functions without repeating the same code
several times. It makes code more reusable because aspects can be reused,
and makes it easier to modify existing code.
Aspect-oriented languages such as AspectJ let you specify precisely when and
where additional code should execute. Such a specification is called a
"pointcut". A "join-point" is any point in code where stuff can be
inserted, for example to insert members into a class, or to execute code before
or after another function. Inserted code is often called "advice".
Implementing all of this in standard C++ would clearly be impossible, which is
why projects such as AspectC++ provide extensions to enable proper AOP in
C++. However we can write C++ in an aspect-oriented style, using template
meta-programming to assemble aspects into executable code. Template
metaprogramming provides an efficient solution with no run-time overheads.
Such techniques fall into the category of "generative programming", which is
where code is customised for you by the compiler based on your specification.
class counter1Like other aspect-oriented systems, we want to insert behaviour before and after functions, into base classes, and be able to address individual functions. Our first attempt is simply to supply the additional advice in template arguments. The template arguments are effectively the pointcuts (the specification points).
{
int count;
public:
counter1() : count(0) { }
void inc(int n=1) { count += n; }
void reset() { count=0; }
int get() const { return count; }
};
template<Here are some aspects we could supply to our counter2 class:
typename Bases,
typename Cons,
typename Inc,
typename Reset,
typename Get>
class counter2 : public base_joinpoint_list<Bases>
{
public:
counter2()
{
joinpoint_list<Cons>(*this);
}
void inc(int n=1)
{
joinpoint_list<Inc>(*this, n);
}
void reset()
{
joinpoint_list<Reset>(*this);
}
int get() const
{
return joinpoint_list_r<Get>(*this);
}
};
// Define the "core aspects" of our counter.
struct counter_base_advice
{
int count;
counter_base_advice() : count(0) { }
};
struct counter_inc_advice : public advice_base<>
{
void operator()(counter_base_advice&c,int n)
{
c.count += n;
}
};
struct counter_reset_advice : public advice_base<>
{
void operator()(counter_base_advice &c)
{
c.count=0;
}
};
struct counter_get_advice : public advice_base<int>
{
int operator()(const counter_base_advice&c)
{
return c.count;
}
};
struct empty;We can then supply a list of advice to our counter as follows:
template<typename H, typename T=empty>
struct list;
typedef list<A, list<B, list<C> > > ABC;
struct logging_advice : public advice_base<>The functions joinpoint_list, and joinpoint_list_r, actually iterate through the type-list, executing the advice in the list.
{
void onBefore(const counter_base_advice &c)
{
std::cout << "Entry count is " << c.count << std::endl;
}
void onAfter(const counter_base_advice &c)
{
std::cout << "Exit count is " << c.count << std::endl;
}
};
typedef counter2<
list<counter_base_advice>,
list<logging_advice>,
list<logging_advice, list<counter_inc_advice> >,
list<logging_advice, list<counter_reset_advice> >,
list<logging_advice, list<counter_get_advice> > >
logging_counter2;
struct counter_pointcut;We can achieve that if we are prepared to number each item in the typelist. We can write a macro which declares a list item in a particular position, and even declare sublists.
add_to_typelist(counter_pointcut, validating_advice); // In validating.h
add_to_typelist(counter_pointcut, locking_advice); // In locking.h
add_to_typelist(counter_pointcut, logging_advice); // In logging.h
template<typename ListName, int Item>We can then write a meta-program to iterate through member_of and sublist_of and assemble a typelist from our named list. (This is an exercise for the reader. Create the list in ascending order. Next, traverse sublists to any depth and add them to your original list. Finally, merge sublists such that elements in the output list appear in ascending order of their slot numbers. [Advanced - solution at http://calumgrant.net/aspects])
struct member_of
{
typedef void type;
};
template<typename ListName, int Item>
struct sublist_of
{
typedef void type;
};
#define ADD_TO_LIST(List, Item, Slot) \
template<> struct member_of<List, Slot> { typedef Item type; };
#define DEFINE_SUBLIST(List, SubList, Slot) \
template<> struct sublist_of<List, Slot> { typedef SubList type; };
#define ATTACH_ADVICE(PointCut, Advice, Priority) \
ADD_TO_LIST(PointCut, Advice, Priority)
// Define some pointcuts
struct constructor_pointcut;
struct inc_pointcut;
struct reset_pointcut;
struct counter_base_pointcut;
struct counter_get_pointcut;
struct all_counter_pointcuts;
// Define relations between pointcuts.
// Here we say that all_counter_pointcuts contains the other pointcuts.
DEFINE_SUBLIST(inc_pointcut, all_counter_pointcuts, 0);
DEFINE_SUBLIST(reset_pointcut, all_counter_pointcuts, 0);
DEFINE_SUBLIST(constructor_pointcut, all_counter_pointcuts, 0);
DEFINE_SUBLIST(counter_get_pointcut, all_counter_pointcuts, 0);
ATTACH_ADVICE(counter_base_pointcut, locking_advice::base_class, 1)
ATTACH_ADVICE(all_counter_pointcuts, locking_advice , 0)
class counter3 : public base_joinpoint<counter_base_pointcut>
{
public:
counter3()
{
joinpoint<constructor_pointcut>(*this);
}
void inc(int n=1)
{
joinpoint<inc_pointcut>(*this, n);
}
void reset()
{
joinpoint<reset_pointcut>(*this);
}
int get() const
{
return joinpoint_r<counter_get_pointcut>(*this);
}
};
Aspect orientation and C++,
Typelists and template metaprogramming,