这是我对pure system的《AspectC++ Language Reference》第二章基本概念的翻译。蓝色表示重点概念,黄色是我不太清楚或我的理解。不知道理解的是否正确。希望csdn上有同学可以一起探讨
第二章 基本概念
2.1 Pointcuts
在AspectC++中,通过Aspects以模块化的方法实现横切点。基于这一点,AspectC++语言中最重要的元素就是pointcut。Pointcut描述了join point的集合,该集合决定了aspect作用的条件。因此join point既可以是函数、属性、类型、变量,也可以是join point开始作用的点,例如到达指定的代码处。根据pointcut类型,pointcu可以在编译时或运行时作用。
2.1.1 匹配表达式
Aspect C++中有两种类型的pointcut:code pointcut和name pointcut。Name pointcut描述一系列静态的已定程序入口,例如类型、属性、函数、变量或名字空间。name pointcut基于匹配表达式。匹配表达式可以被理解为搜索模式。在该搜索模式中,字符“%”为通配符,“…”用来匹配函数声明中任何数目的参数。匹配表达式是用引号勾住的字符串。
例子:匹配表达式(name pointcut)
"int C::%(...)"
匹配所有类C中返回值为int的成员函数;
"%List"
匹配任意以“List”为结尾的类、结构、联合或枚举;
"% printf(const char *, ...)"
匹配全局域中名字为print、拥有至少一个const char *型形参、返回任意类型的函数;
"const %& ...::%(...)"
匹配所有返回值为const reference的函数;
匹配表达式根据定义域、类型和名字选择程序入口点。匹配表达式的细节描述见第三章。
2.1.2 pointcut 表达式
code pointcut是pointcut的另一种类型。code pointcut可以指函数的调用点或执行点。必须通过name pointcut才能创建code pointcut,因为AspectC++中定义join point时都需要一个名字。pointcut函数是例如:within(pointcut)这样的东西, pointcut需要在调用该函数之前定义。在pointcut表达式中包含了pointcut函数。
name pointcut和code pointcut通过代数运算符“&&”、“||”和“!”组合。
例子:pointcut表达式
"%List" && !derived("Queue")
描述以List结尾、不派生自基类Queue的所有类;
call("void draw()") && within("Shape")
描述所有对类Shape中void draw()方法的调用;
2.1.3 join point的类型
由于Aspect C++中有两种类型的pointcut,因此也有两种类型的join point(code&name)。这两种类型间的区别和联系通过以下代码说明:
class Shape;
void draw(Shape&);
namespace Circle {
typedef int PRECISION;
class S_Circle : public Shape {
PRECISION m_radius;
public:
...
void radius(PRECISION r) { m_radius=r; }
};
void draw(PRECISION r) {
S_Circle circle;
circle.radius(r);
draw(circle);
}
}
execution join point与可执行函数名关联。纯虚函数不可执行,因此可执行join point的advice代码不能应用于纯虚函数。但纯虚函数的调用,即这类函数作为call join point,是可以的。
call join point与两个名字相关:源(调用者)名字和调用的目标函数(被调用者)的名字。由于同一个函数中会有很多函数调用,每个函数名可以被关联到一系列的call join point。construction join point为当创建实例时。同理,destruction join point对应于析构实例。
2.1.4 pointcut声明
Aspect C++中利用pointcut声明给pointcut表达式指定名字,方便pointcut表达式重用。poitncut声明的被允许的情况和C++中的声明一样。因此C++中的名字查找和继承规则同样适用于pointcut声明。
pointcut声明以关键字pointcut标识。
例子:pointcu声明
pointcut lists() = derived("List");
声明后,程序中需要derived(“List”)的地方就可以用lists表示。
通过pointcut声明,可定义纯虚pointcut,因此可以实现可重用的抽象aspect。纯虚pointcut的语法和一般pointcut的相同,但要在关键字pointcut前面加virtual,且pointcut表达式为0。
例子:纯虚pointcut声明
pointcut virtual methods() = 0;
methods为纯虚pointcut,需要在派生aspect中重新定义为实际的pointcut表达式。
2.2 Slices
slice是C++语言元素中定义一个范围的段落。advice可利用slice扩展程序的静态结构。例如,利用advice,class类型slice中的元素可被加到一个或多个目标类中。下例说明如何声明class类型的slice。
例子:class类型slice的声明
slice class Chain {
Chain *_next;
public:
Chain *next () const { return _next; }
};
2.3 Advice代码
advice代码可绑定到code join point。advice代码可被理解成aspect中的一个活动,当到达程序中相应code join point时,就会触发这个活动。advice代码活动可以发生在code join point到达之前、之后、或者之前和之后。Aspect C++语言通过advice声明指定advice代码。advice声明以关键字advice打头,后跟pointcut表达式,pointcut表达式定义了advice代码活动的地方和活动的条件。
例子:advice声明
advice execution("void login(...)") : before() {
cout < < "Logging in." < < endl;
}
pointcut表达式后的代码段“:before()”表明,advice代码再code join point到达前执行。“:after()”同理。“:around()”表示执行advice代码,不执行code join point代码。这种情况下,如果需要执行code join point代码,需要显示的通过proceed()指明。程序中的代码没有访问advice代码的权限。
除了单纯描述join point,pointcut也可以绑定表示join point的上下文信息的变量(我想也就是函数参数吧)。这样advice代码就可以访问这个函数的实参了。
例子:可以访问上下文信息的advice声明
pointcut new_user(const char *name) = execution("void login(...)") && args(name);
advice new_user(name) : before(const char *name) {
cout < < "User " < < name < < " is logging in." < < endl;
}
上例中,首先,定义new_user为包含上下文变量“name”的pointcut。这意味着,每当到达new_user所规定的join point时,都会被提供const char *类型的变量。表达式中的args为pointcut函数,它传递程序中所有包含const char *类型参数的join point。因此和execution join point联系的args(name)会绑定name到函数login的第一个(并且只绑定到这一个)参数。
上例中advice声明将advice代码绑定到一个事件,这个事件就是:到达new_user所描述的join point。持有join point的参数实际值的上下文变量(也就是name)必须在before、after或around中作为正式参数声明。该参数(name)可在advice代码中作为普通函数参数使用。
除了pointcut函数args,还有that、target和result可以绑定上下文变量。这些pointcut函数还可以根据变量类型进行筛选。例如例子中的args筛选具有const char *类型参数的join point。
2.3.1 Introduction
AspectC++支持的第二种advice是introduction。introduction用于扩展程序代码和数据。下例中,对两个class扩展了属性和方法。
例子:introduction
pointcut shapes() = "Circle" || "Polygon";
advice shapes() : slice class {
bool m_shaded; 1
void shaded(bool state) {
m_shaded = state;
}
};
和普通advice声明相同,introduction也以关键字advice打头。如果advice后的pointcut是name poitncut,那么“:”之后的slice声明就被引入(introduce)到pointcut所规定类或aspects中。使用引入的代码和使用普通程序代码一样。introduction中的advice代码拥有join point处程序代码的完全访问权限,即,引入到class中的方法能够访问该class的私有成员。
slice还能用于为class引入新基类。下例中第一行使得以”Object”结尾的类都派生自类”MemoryPool”。 ”MemoryPool”可能通过从在new、delete运算符实现了独立的内存管理。继承自MemoryPool的类必须重定义纯虚函数release,release是实现内存管理的一部分。这在所有pointcut中规定的类的第二行完成。
例子:引入基类
advice "%Object" : slice class : public MemoryPool {
virtual void release() = 0;
}
2.3.2 Advice顺序
如果对同一个join point有多个advice,且advice代码间存在依赖性(aspect交互),那么就需要定义advice的执行顺序。下例说明了如何定义advice顺序。
例子:advice排序
advice execution("void send(...)") : order("Encrypt", "Log");
Encrypt和Log是aspect,如果void send(…)函数被执行时,这两个aspect中都有advic需要执行,那么Encrypt中的具有高优先级。关于advice的顺序详见第八章。
2.4 Aspect
AspectC++中的aspect集合了introduction和advice代码,以模块化方式实现普通横切关注点。aspect是管理普通状态信息的。aspect通过声明呈现,好像C++中class概念的扩展一样。aspect的基本结构定义和C++的class定义一样,除了用aspect替换class、struct或union。因此,aspect也有属性,也有方法,也能继承自其它class或其他aspect。
例子:aspect声明
aspect Counter {
static int m_count;
Counting() : m_count(0) {}
pointcut counted() = "Circle" || "Polygon";
advice counted() : class Helper {
Helper() { Counter::m_count++; }
} m_counter; /*说明:这个advice为每个join point加入一个data成员:m_counter,这个成员是Helper类类型的对象,它在初始化的时候调用Helper构造函数,因此m_counter就会加1。*/
advice execution("% main(...)") : after() {
cout < < "Final count: " < < m_count < < " objects" < < endl;
}
};
上例用于计算Circle 和Polygon 这两种class的实例对象的数目。因此在pointcut所描述的class中引入一个属性(m_counter),用来在每次构造函数调用的时候使全局量m_count加1。通过在main函数上应用advice码,可以在程序结束时显示对象实例个数。
计算对象实例个数的例子也可以被重新改写为抽象的aspect,这样可以存档以便重用。只需要将pointcut声明为纯虚就可以了。
例子:抽象aspect
aspect Counter {
static int m_count;
Counting() : m_count(0) {}
pointcut virtual counted() = 0;
...
};
这样以后可以重用这个计数功能,只要通过继承Counter,并重新实现counted,使他指向实际的pointcut表达式。
例子:重用抽象aspect
aspect MyCounter : public Counter {
pointcut counted() = derived("Shape");
};
2.4.1 aspect实例化
默认情况下,Aspect C++中的aspect被自动实例化为全局对象。这样做的思想是:aspect也能够提供全局程序属性,因此应该是永远可访问的。但是,在一些特殊情况下,可能需要改变这种全局的行为,例如,在操作系统环境中,可能需要每个进程(或线程)都实例化一个aspect对象。
默认的实例化机制可通过定义静态方法“aspectof”改变。aspectof可返回aspect的实例。
例子:使用aspectof获得aspect实例
aspect ThreadCounter : public Counter {
pointcut counted() = "Thread";
advice counted() : ThreadCounter m_instance; /*给每个Thread引入一个m_instance,它是aspect的实例*/
static ThreadCounter *aspectof() { //为什么返回的是个指针呢?
return tjp->target()->m_instance;
}
};
向Thread中引入m_instance保证了每个thread对象都有一个aspect实例。通过调用aspectof,可以获得该实例,这对访问advice代码和aspect成员非常关键。aspectof中的代码拥有访问实际join point的能力。(类似singleton设计模式)
2.5 运行时支持
2.5.1 对advice代码的支持
对一些aspect而言,仅仅访问上下文变量(函数参数)或许不能获得关于join point的足够多的信息。例如,用于完整记录程序中函数调用的控制流aspect,会需要关于函数参数和参数运行时类型的信息,才能产生类型兼容的输出。
Aspect C++中,这些信息可以通过类JoinPoint中的成员获得。
对于每次advice代码执行,上表中的类型和静态方法所返回的信息都是一样的,非静态方法返回的信息则是不同的。这些方法通过对象tjp(thisJoinPoint)访问,tjp是JoinPoint类型对象,在advice代码中可以访问。
下例说明了如何使用JoinPoint API实现可重用的控制流aspect。
例子:可重用的trace aspect
aspect Trace {
pointcut virtual methods() = 0;
advice execution(methods()) : around() {
cout < < "before " < < JoinPoint::signature() < < "(";
for (unsigned i = 0; i < JoinPoint::args(); i++)
printvalue(tjp->arg(i), JoinPoint::argtype(i));
cout < < ")" < < endl;
tjp->proceed();
cout < < "after" < < endl;
}
};
这个aspect将trace代码织入到每个pointcut指定的函数中,pointcut是虚的,需要在派生aspect中重新定义。辅助函数printvalue用于将函数调用中的参数格式化输出。对所有参数调用过printvalue后,通过在JoinPoint上调用proceed函数,执行实际的join point。
2.5.2 Actions
Aspect C++中,action是指:在程序运行时,跟随在到达的join point之后的语句序列。tjp->proceed()触发join point程序代码的执行。可以是函数调用或函数执行。action概念通过AC::Action结构实现。实际上,proceed()等价于action().trigger(),因此tjp->proceed()可以被tjp->action()->trigger()替换。JoinPoint API的action()函数,返回join point的实际action对象。