第三章 匹配表达式
匹配表达式用来描述Aspect C++程序中,静态的已知程序实体。可以用来匹配函数,也可以用来匹配类型。这里class也被视为一种类型。
对函数匹配而言,与匹配表达式被分解为:函数类型样式,作用域样式,以及名字样式。
例子:函数匹配表达式的类型、作用域、和名字部分
"const % Puma::...::parse_% (Token *)"
上例中的匹配表达式描述了在比较函数名字时的需求:
名字:函数名字要匹配名字样式:“parse_%”;
作用域:函数的作用域需要匹配:“Puma::…::”;
类型:函数类型需要匹配:“const %(Token *)”
class和其他类型不需要这样的分解。例如,类型名:“Puma::CCParser”已经足够用来描述一个class,因为这和class的名字一样。
如果实体和匹配表达式完全匹配,那么这个实体就是匹配表达式所定义的集合中的一员。
用来解析匹配表达式的语法参见P33附录B。以下几小节分别介绍名字、作用域、以及类型的匹配机制。注意,名字和作用域匹配不仅用于匹配函数名,还用于匹配类型名,例如class。
3.1 名字匹配
3.1.1 简单名字匹配
如果比较的名字是普通的C++标识符,那么名字匹配就非常简单,不值一提。如果“名字样式”中不包含通配符“%”,那么就精确匹配名字。否则,通配符可以匹配任意字符,包括空字符。
例子:简单名字匹配
Token 只匹配Token;
% 匹配任意名字;
pars_% 匹配任意以“parse_”开头的名字,例如“parse_declarator”或”parse_”;
parse_%_id% 匹配例如“parse_type_id”,“parse_private_identifier”等名字;
%_token 匹配所有以”_token”结尾的名字,例如“start_token”,“end_token”,以及“_token”。
3.1.2 运算符重载函数和类型转换函数的名字匹配
运算符重载函数和类型转换函数的名字匹配相对复杂。他们都通过名字样式“%”匹配,尽管如此,如果样式以”operator”开头,除了“%”,还可以用不同的名字样式。样式:“operator %”可以匹配任何运算符重载函数和类型转换函数。
C++中已经定义了允许重载的运算符集合,这些运算符可以跟在“operator”之后,用于匹配运算符函数。名字样式中的运算符名字不能包含通配符,如果要重载运算符“%”或“%=”,那么名字样式中应该使用“%%”或“%%=”。
例子:运算符名字样式
operator % 匹配任何运算符重载函数名(以及任何类型转换函数名);
operator += 只匹配运算符“+=”函数名;
operator %% 匹配运算符“%”函数名。
类型转换函数没有实际名字。例如,class C中定义的类型转换函数“operator int *()”,定义了从C到int *类型的转换。为了匹配类型转换操作符,名字样式中,“operator”之后会包含类型样式。类型匹配机制在3.3节中阐述。
例子:类型转换函数名字样式
operator % 匹配任何运算类型转换函数名;
operator int * 匹配转换到int *的类型转换函数;
operator %* 匹配转换到指针的类型转换函数,指针可以是任何类型的。
3.1.3 构造函数和析构函数
名字样式不能用于匹配构造函数或析构函数。
3.1.4 作用域约束
匹配表达式中,可以在名字样式的前面加上作用域样式。作用域样式(见3.2节)用于描述对匹配实体定义范围的约束。如果没有给出作用域样式,就匹配定义在全局作用域中的函数或类型。
3.2 作用域匹配
作用域样式描述了对定义域的约束。作用域样式是由“::”分隔的一系列名字样式(或任意作用域序列样式“…”),例如“Puma::…::”。作用域样式以“::”结尾,不能以“::”开头,因为作用域样式是在全局作用域中进行解析的。定义域可以是名字空间,也可以是类。
如果其他部分都匹配,则作用域样式对函数或类型的定义域进行匹配。匹配的限定名与全局域关联,不能以”::”开头。”…”样式匹配任何(包括空的)作用域名字序列。
例子:作用域样式
…:: 匹配任何定义域,包括全局域;
Puma::CCParser:: 精确匹配作用域:“Puma::CCParser”
…::%Compiler%:: 匹配任何作用域中,匹配“%Compiler%”名字的作用域;
Puma::…:: 匹配定义在Puma中(或Puma本身)的作用域,包括Puma本身,Puma是类或名字空间。
3.3 类型匹配
3.3.1 匹配机制
C++类型可用树表示。例如,函数类型int(double)是一个函数类型的节点,他有两个孩子,一个事int节点,一个是double节点,这两个孩子节点都是树的叶节点。
匹配表达式中使用的类型也可以被理解为树。作为普通C++类型的扩展,匹配表达式中的类型也可以使用通配符“%”、名字样式、以及作用域样式。类型样式中的单个通配符在树中表示“任意类型节点”。
例子:包含通配符的类型样式
% 匹配任何类型;
void (*)(%) 匹配任何函数指针类型,该函数指针所指向的函数具有单个参数,返回值为void;
%* 匹配任何类型的指针;
3.3.2 命名的类型的匹配(Matching of Named Types)
类型样式也可以包含名字样式和作用域样式。成为树中命名的类型节点,匹配任何union、struct、class或枚举类型,前提是这些类型的名字样式和作用域样式都已经匹配。
3.3.3 “指向成员的指针”类型的匹配(Matching of “pointer to member” Types)
指向成员的指针的样式也包含作用域样式,例如:“% (Puma::CSyntax::*) ()”。这里的作用域样式是必须要有的。
3.3.4 被限定词修饰的类型的匹配(const/volatile)
许多C++类型被限定为const或volatile。在类型样式中,也可以使用这些限定词作为约束。如果类型样式中没有const或volatile限定,样式也匹配具有这些限定词的类型。
例子:包含const和volatile的类型样式
% 匹配任意类型,包括被const或volatile修饰的类型;
const % 只匹配被const修饰的类型;
% (*)() const volatile 匹配被const和volatile修饰的函数指针类型。
3.3.5 处理转换函数类型
类型转换函数的结果类型是特殊的未定义类型。这个未定义类型只和“任意类型”和“未定义类型”匹配。
3.3.6 函数类型样式中的省略号
在函数参数列表中,类型样式“…”用于匹配任意(包括空)的类型列表。参数类型列表中,“…”之后不能再跟其他参数类型样式。
3.3.7 虚函数的匹配
函数类型匹配表达式可以包括关键字“virtual”。这是,函数类型匹配表达式只匹配虚函数或纯虚函数。和const、volatile一样,virtual关键字也是一种约束。没有virtual关键字的函数类型匹配表达式既可以匹配虚函数,也可以匹配非虚函数。
例子:包括virtual的类型样式
virtual % …::%(…) 匹配任意作用域中的虚函数和纯虚函数;
% C::%(…) 匹配C类中的所有成员函数,包括虚函数。
3.3.8 参数类型调整
类型样式中的参数类型根据正常C++规则进行调整,即,数组和函数类型被转换为给定类型的指针,且限定词const/volatile被移除。另外,如果类型样式中的参数列表是void,也就是诶有参数,那么就会被转化为空参数类型列表。
第四章 预定义的Pointcut函数
本章介绍Aspect C++中定义的所有pointcut函数。介绍了每个函数所需要的参数类型和返回值类型。“N”表示name pointcut,“C”表示code pointcut。:Code (any, only Call, only Execution, only Set, only Get); :Names (any, only Namespace, only Class, only Function, only Type)
4.1 类型
返回所有pointcut类的所有基类;
返回pointcut中的所有类和它们的派生类。
例子:类型匹配
代码中可能包含以下类层次:
class Shape { ... };
class Point : public Shape { ... };
...
class Rectangle : public Line, public Rotatable { ... };
通过下面这个aspect,会向上面这个类层次中的某些类中添加特殊属性。
aspect Scale {
pointcut scalable() = (base("Rectangle") && derived("Point")) || "Rectangle";
advice "Point" : baseclass("Scalable");
advice scalable() : void scale(int value) { ... }
};
上面这个pointcut指定了类Point、Rectangle以及所有派生自Point且是Rectangle直接或间接基类的类。第一个advice使Point获得一个新基类。第二个advice为pointcut中的所有类增加了一个函数scale。
4.2 控制流(Control Flow)
cflow(pointcut) C->C
捕获动态执行环境中join point的发生。参数不能包含环境变量绑定(参见4.6),不能包含其他需要在运行时计算的pointcut函数,例如cflow(pointcut)。
例子:依赖控制流的advice执行
下例展示如何使用cflow函数。
class Bus {
void out (unsigned char);
unsigned char in ();
};
考虑上例中所示的Bus类。它可能是os内核的一部分,用于通过I/O总线访问外围设备。成员函数in()和out()的执行不能被中断,否则会打断总线通信的计时。因此需要实现一个中断同步aspect,从而禁止in()和out()执行过程中的中断。
aspect BusIntSync {
pointcut critical() = execution("% Bus::%(...)");
advice critical() && !cflow(execution("% os::int_handler()")) :around() { /*在in() out()执行且非中断处理时,先关中断,执行,再开中断。*/
os::disable_ints();
tjp->proceed();
os::enable_ints();
}
};
因为中断处理(interrupt)也会调用总线驱动,所以不能不管什么时候都关中断。因此在pointcut表达式中引入cflow()函数,从而为advice代码的执行添加运行时条件。advice只有在控制流不是来自中断处理os::int_handle()的时候才会执行,因为这是不能中断的,而且如果是中断处理中,执行advice代码中的os::enable_ints()会造成中断过早的被打开。
4.3 范围(Scope)
within(pointcut) N->C
筛选在pointcut中的joint point,pointcut可以是函数或类。
例子:在范围中匹配
aspect Logger {
pointcut calls() = call("void transmit()") && within("Transmitter");
advice calls() : around() {
cout < < "transmitting ... " < < flush;
tjp->proceed();
cout < < "finished." < < endl;
}
};
这个Aspect向类Transmitter中的成员函数transmit中插入代码,记录对transmit的调用。
4.4 函数
call(pointcut) N->Cc
它所指定的join point是:pointcut指定的实体被调用。这个pointcut既可以包含函数名也可以包含类名。如果是类名,那么所有对该类方法的调用都是join point。
它所指定的join point是:pointcut指定的实体被实现。这个pointcut既可以包含函数名也可以包含类名。如果是类名,那么所有对该类方法的实现都是join point。
例子:函数匹配
这个aspect向程序中织入调试代码,以检测方法是否是对null指针的调用以及参数是否为null。
aspect Debug {
pointcut fct() = "% MemPool::dealloc(void*)";
pointcut exec() = execution(fct());
pointcut calls() = call(fct());
advice exec() && args(ptr) : before(void *ptr) {
assert(ptr && "argument is NULL");
}
advice calls() : before() {
assert(tjp->target() && "’this’ is NULL");
}
};
第一个advice在函数dealloc执行前检查函数的参数是否为NULL。第二个advice检查dealloc是否是对null对象进行的调用。通过检查call的target实现。
4.5 对象构造和析构
join point是:pointcut所给定的class的对象被构造。construction join point在所有基类和成员construction join point之后开始。construction(pointcut)可以认为是构造函数的执行。但即使没有显示定义构造函数,construction joinpoint的advice也会执行。construction join point拥有参数和参数类型,可以被引用或筛选,例如,通过使用args函数。
join point是:pointcut所给定的class的对象被析构。destruction join point在成员和基类的destruction join point之前结束。destruction join point可以被视为析构函数的执行,尽管析构函数可以不显式定义。destruction join point没有参数。
例子:计算实例对象个数
下面这个aspect计算有多少个ClassOfInterest类的实例对象被创建和解析。
aspect InstanceCounting {
// the class for which instances should be counted
pointcut observed() = "ClassOfInterest";
// count constructions and destructions
advice construction (observed ()) : before () { _created++; }
advice destruction (observed ()) : after () { _destroyed++; }
public:
// Singleton aspects can have a default constructor
InstanceCounting () { _created = _destroyed = 0; }
private:
// counters
int _created;
int _destroyed;
};
这个aspect的实现非常直观。两个计数器在aspect构造函数中被初始化,并分别在construction/destruction advice中被递增。将observed()定义为纯虚pointcut,方便以后重用。
4.6 上下文(Context)
that(type pattern) N->C
返回join point,在这个join point处,this指针指向一个对象,这个对象的类型是与type pattern中描述的匹配的类型。
target(type pattern) N->C
返回join point,在这个join point处,call的目标对象是一个对象,这个对象的的类型是与type pattern中描述的匹配的类型。
result(type pattern) N->C
返回join point,在这个join point处,call/execution的结果类型是一个对象,这个对象的的类型是与type pattern中描述的匹配的类型。
args(type pattern,…) (N,…)->C
args的参数列表包含了type pattern,这些type pattern用于筛选join point。例如,用于包含声明式匹配的函数调用或函数执行中。
除了type pattern,也可以传递绑定上下文信息的变量名字(上下文变量)。在这种情况下,变量的类型用于类型匹配。上下文变量需要在before()、after()、arount()的参数列表中声明,并可以在advice体内像使用函数参数一样去使用这些变量。
that()和target()这两个函数比较特别,因为他们会引起运行时类型检查。args()和result()函数在编译期间求值。
4.7 逻辑操作符
pointcut && pointcut (N,N) -> N, (C,C) -> C 且关系
pointcut || pointcut (N,N) -> N, (C,C) -> C 或关系
! pointcut N -> N, C -> C 非关系