??xml version="1.0" encoding="utf-8" standalone="yes"?> #include <cstdio> typedef int (*fun)(int); #include <cstdio> typedef void (*fun)(int i,int n); void f1(int i,int n) printf("%d\n",i); int main() #include <cstdio> void f(int i,int n) int main()
Flexible C++
C++是一门非常灵zȝ语言Q只要充分发挥你的想象, 再普通的东西都能玩出新花?br>
1?~1000求和
循环Q递归Q再单不q的题目了。但是如果不允许你用判断语句呢?
如果你熟悉switch的内部实玎ͼ那么你很Ҏ惛_使用函数指针数组?br>
int f1(int i) {return 0;}
int f2(int i) {fun f[2]={f1,f2}; return i+f[!!i](i-1);}
int main()
{
printf("%d\n",f2(1000));
}
如果同样不让你用判断语句呢?你仍然可以用函数指针数l:
void f1(int i,int n);
void f2(int i,int n);
void f3(int i,int n);
{
fun f[2]={f1,f2};
f[i+1==n](i+1,n);
}
void f2(int i,int n)
{
fun f[2]={f2,f3};
printf("%d\n",i);
f[i==1](i-1,n);
}
void f3(int i,int n) {}
{
f1(1,100);
}
{
printf("%d\n",i),(i<n)&&(f(i+1,n),printf("%d\n",i));
}
{
f(1,100);
}
]]>
]]>
从系l结构上来讲Q?/span> C/C++ 支持 3 U内存管理方式,Z栈的自动理Q基于堆的动态管理,和基于全局区的静态管理。由?/span> RAII 的理念,对于 C++ 来说Q内存管理和其他资源理本质上没有区别。因此对于资源而言Q也p然的拥有q样 3 U管理方式?/span>
首先要的介绍一?/span> RAII ?/span> RAII 的全U是 Resource Acquisition Is initialization 。这个思想的基本手法是对于一U想要用的资源Qؓ其书写一?/span> guard c,在该cȝ构造函数里q行资源的请求,在析构函数里q行资源的释放。例如假设我们想理一个互斥锁Q可能的方式是:
struct lock_guard
{
lock_guard()
{ lock ();}
~ lock_guard()
{unlock();}
} ;
此后Q对q个对象使用什么内存管理方式,也就{h于对q个互斥锁用什么内存管理方式?/p>
借助?/span> RAII Q以后我们可以只讨论内存资源的管理方式,其它资源的管理方式可以?/span> RAII 来同L实现。现在我们已l很自然的获得了资源理?/span> 3 U方式:Z堆的动态方式、基于栈的自动方式和全局。值得一提的是,q?/span> 3 U方式中比较不容易出错的后两U实际上可以解决大部分的资源理需求。因为绝大部分资源,都属于获?/span> - 使用 - 释放型的Q例如很多同步对象,文g锁, WinGDI 里的许多 GDI 对象。我们缺乏管理的Q只有那些一ơ获得,多个环境拥有Qƈ且只能有一ơ释攄数资源?/span>
回到内存模型来看Q有一点让我们无法内存与其它资源{同Q反q来Q把其它资源和内存等同却是可以的Q,那就是@环引用?/span> A 内存可以持有指向 B 内存的引用, B 内存也可以反q来持有 A 内存的引用。@环引用导致内存管理不可以?#8220;是否有指向该内存的引?#8221;来区分一块内存是否可以回收。从而׃一个绝佳的理手段。但是在没有循环引用的场合下Q我们还是有非常z高效的理Ҏ的。那是引用计数?/span>
引用计数是在没有循环引用场合下进行内存管理的l佳手段Q它h轻量、高效、即时、可控的优点。而且?/span> C++ 里,引用计数已经非常成熟Q只需要?/span> boost.shared_ptr 或者其它非官方的引用计数指针库可以了Q而且据悉 C++09 很可能把 boost.shared_ptr U_标准库。引用计数的原则是,如果一个对象没有别的指针或引用来指向它Q那么这个对象就是可以释攄。具体的手法有大把大把的资料可以查阅Q这里就不详l说明了。引用计数通常可以处理哪些场合的资源管理问题呢Q首先,对于单方向的资源理Q也是多个 A 的实体拥?/span> 1 ?/span> B Q然?/span> B q不会反q来依赖?/span> A Q例如多个对象共享一个日志)Q引用计数是非常合适的。其ơ,对于拥有 - 反作用的场合Q也是 1 个或多个 A 的实体拥?/span> 1 个或多个 B Q?/span> B 也拥有这?/span> A 的实体的引用Q但?/span> B 的生存期仍然军_?/span> A 的生存期Q例如父H口拥有若干子窗口,子窗口也h parent 指针指向父窗口,但是子窗口的生存期决定于父窗口的生存期)Q这个时?/span> A 可以?/span> B 使用引用计数指针Q?/span> B 可以?/span> A 使用原生的普通指针,同样的可以很好的解决问题?/span>
现在所剩下的,只有生存期的@环依赖了。如?/span> AB 互相持有Ҏ的引用,而且 AB 互相的存在都依赖于对方,q样引用计数无法解决了。但是如果仔l想一下就会发玎ͼq种情况?/span> C++ 里几乎不可能存在。生存期循环依赖只有 2 U后果,要么 A ?/span> B 的析构函数里互相析构Q当然就挂了Q,要么互相都不析构Q当然就泄露了)。而这两种都是在正常编E中不会出现的情c所以如果即使仅仅用引用计敎ͼ我们也可以解军_乎所有的资源理问题?/span>
现在q剩下那么一丁点极少出现的不能处理的情况Q我们可以用更加复杂的 gc 来实现。可惜的是,实现一?/span> gc 所要耗费的精力实在太大,而且几乎不可避免的要成ؓ侵入式的库。所以有点得不偿失。而且 gc 通常会生更多的毛病Q?/span>
1Q?/font> 你无法却知对象析构的具体旉Q从而无法真正知道媄响程序性能的瓶颈在什么地斏V?/span>
2Q?/font> gc 都們于大量的使用内存Q直到内存不够的时候再q行清理Q这样会DE序的内存用量严重颠,q且产生大量的换c?/span>
3Q?/font> q度的依赖于 gc 会ɽE序员大量的把可以由之前提到的各U方法来处理的资源交l?/span> gc 来处理,无故的加重了 gc 的负担?/span>
4Q?/font> gc 的管理方法和 C++ 的析构函数有可能产生语义上的冲突?/span>
q就是ؓ什?/span> C++ 标准对垃圑֛收的态度如此恶劣的原因?/span>
我们现在回过头来?/span> Java/C# q样的内|?/span> gc 的语a。这L语言׃使用?/span> gc Q就不可避免的放弃了析构函数。ؓ什?/span> gc 会和析构函数产生冲突呢?一?/span> gc 一般会希望在进行垃圑֛收的时候,整个q程是一个原子的Q但析构函数会破坏这一点,在释攑ֆ存的时候如果还要执行代码,那么隑օ会对整个 gc 环境产生破坏性的影响。由于没有析构函敎ͼq些语言׃可能做到 RAII Q也是_它们?/span> gc 所能够理的,也就仅仅只有内存而已了。对于其他资源, Java {就必须手动释放。虽?/span> C# 提供?/span> with 关键字来~解q一问题Q但仍然无法d的解冟?/span>
q有什么麻烦呢Q之前说的那 4 点全部都有。虽?/span> JVM 的速度在不断的提高Q但是内存用这一点却完全没有发展Q不能不说是 gc 说导致。它所带来了什么好处呢Q是内存理的自动化Q而不是资源管理的自动化?/span>
所以说 C++ q不是世人所惌的那样需?/span> gc Q?/span> C++ 本n已l提供了_强大的资源管理能力。基于栈的自动管理,或者用引用计敎ͼ几乎可以辑ֈ?/span> gc 同样的覆盖面Q而且没有 gc 的那些问题, RAII 使得 C++ 在管理非内存资源的时候还更加有优势,Z么不使用呢?
ps. 设计一个非官方?/span> gc 库还是可以的。但是毕竟不会成Z了?/span>
用法举例
bit_mask<INT_LIST_2(2, 3)>::valueq回一个|该值的W??位被|ؓ1
其余位ؓ0
sample
#include < iostream >
#include " multi_bit_mask.h "
using namespace std;
int main()
{
cout << multi_bit_mask::bit_mask < INT_LIST_1( 1 ) > ::value << endl;
cout << multi_bit_mask::bit_mask < INT_LIST_5( 0 , 1 , 2 , 3 , 4 ) > ::value << endl;
cout << multi_bit_mask::bit_mask < INT_LIST_7( 0 , 1 , 2 , 3 , 4 , 4 , 2 ) > ::value << endl;
在vc下,使用typeid的时候,如果typeid施加l的cd是没有vptr的class或者根本不是class
那么汇编?br>mov dword ptr [addr],offset A `RTTI Type Descriptor' (42AD40h)
也就是编译器生成一个简单的type_info对象的表Qƈ且在~译期静态决定下标,做一个简单查表操作?br>
如果typeid的操作对象是hvptr的classQ但是ƈ不是一个引用或者指针的解引用Ş式,例如
A a;
typeid(a);
那么仍然仅仅会做查表操作
如果typeid的操作对象是hvptr的classQƈ且是引用或者指针的解引用Ş式,例如
A * p = new A;
A & r = * p;
typeid( * p);
typeid(r);
那么׃调用一个叫___RTtypeid的函敎ͼq过某种Ҏ来获取type_info对象
下面是___RTtypeid的反汇编Q这里只列出关键的几条指?br>
0041213E mov ecx,dword ptr [inptr] Qinptr是对象的地址
00412141 mov edx,dword ptr [ecx]
00412143 mov eax,dword ptr [edx - 4 ]
0041215F mov ecx,dword ptr [eax + 0Ch]
00412162 mov dword ptr [ebp - 48h],ecx
0041216C mov eax,dword ptr [ebp - 48h]
基本上等价于C语言?br>
int a1 = ( int )p; // p是对象的地址
int a2 = * ( int * )a1 - 4 ;
int a3 = * ( int * )a2 + 12 ;
int a4 = * ( int * )a3;
那么从这D代码可以看出vc下type_info对象的存放位|[如下图]
也就虚表下标?1的位|上存放了一个指向一个未知的表的指针(暂且此表命名ؓruntime_info_table)
runtime_info_table的第4g存放了type_info对象的地址
至于runtime_info_table里前3g存放的是什? q需要再研究研究
一般来说它们全?, 但是对于多重虚承的c? W二g会是4, 可能和指针的偏移量有?
q是使用Xpressive动态语义定义的例子Q其中sregex::compile函数~译一个表C正则文法的Ԍq返回一个正则对象sregex
使用regex_match来用这个正则对象匹配一个串。结果储存在what?br>其中what[0]q回整个Ԍwhat[1]~what[n]q回文法中用于标记的部分(用小括号括v来的部分)
最后将输出
hello world!
hello
world
如果惛_一个串中查扄合该文法的子Ԍ可以使用regex_searchQ用法和regex_match一P此外q可以用regex_replace来进行替换?br>
静态文法:
Xpressive除了可以用compile来分析一个文法串之外Q还可以用类gSpirit的方式来静态的指定文法Q?br>
sregex re = '$' >> +_d >> '.' >> _d >> _d;q将定义一个表C金额的Ԍ其中_d表示一个数字,相当于串 $\d+.\d\d
q样定义文法比之前的动态定义更加高效,q且q有一个附加的好处Q?br>分定义Q?/p>
sregex re = '$' >> +_d >> '.' >> _d >> _d;
sregex s = '(' >> re >> ')';q样s表示为用括号括v来的re
通过分定义Q文法能被表C的更加清楚?br>更加的是,分定义q可以向后引用,因此能够分析EBNF
sregex group, factor, term, expression;
group = '(' >> by_ref(expression) >> ')';
factor = +_d | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
expression = term >> *(('+' >> term) | ('-' >> term));expression定义了一个四则表辑ּQ注意其中group的定义?br>q里必须使用by_ref是因为Xpressive默认是值拷贝,如果q里使用默认的方式,那么会造成一个无限@环?br>
Xpressive可以在这里下?br>http://boost-consulting.com/vault/index.php?PHPSESSID=f1d4af8b742cfa7adae7aab373cfc535&direction=0&order=&directory=Strings%20-%20Text%20Processing&PHPSESSID=f1d4af8b742cfa7adae7aab373cfc535
内有详细的文?/p>
很多时候写一个类的时候,需要多个模版参敎ͼ例如一个遗传算法的法c,需要一个模版参数来指定交配方式Q另一个模版参数来指定子代选择的方式,q要一个参数来指定变异的方式。那么一般来_q个cM写成Q?/p>
template<class T //描述问题的一个类
, class CrossPolicy = AvgCrossPolicy //杂交方式
, class SelectPolicy = DefaultSelectPolicy //子代选择的方?br> , class VariationPolicy = ReverseVariationPolicy> //变异方式
class Gene
: private AvgCrossPolicy
, private SelectPolicy
, private VariationPolicy
{
....
};
q样用户要用该cȝ时候,可以直接指定TQ就行了Q然而如果要指定变异方式Q那么就必须把所有的参数都显式的写出来,很不方便
546提供了一U有效的ҎQ可以让我们仅仅指定变异参数Q而不用写出另两个Policy
甚至允许我们以Q意的序书写几个Policy参数Q都不会有问?/p>
预备知识:
TypeList
一个TypeList是一个类型的容器
template <typename Type_, typename Next_>
struct TypeList
{
typedef Type_ Type;
typedef Next_ Next;
};
q就是一个TypeList?br>看这个写法,是不是像一个链表?
首先定义一个类型来表示链表:class NullType{};
现在一个包含了2个类型的TypeList可以写为:
TypeList<T1, TypeList<T2, NullType> >
如何在一个TypeList中查找一个类型的子类Q?br>首先要有一个IsDerivedFrom<Base, T>
q个比较?br>template<class Base, class T>
class IsDerivedFrom
{
struct large{char a[2];};
static char pred(Base*);
static large pred(...);
public:
enum {Is = sizeof(pred((T*)0)) == sizeof(char)};
};
然后FindChild容易了
template <class List, class Base>
struct FindChild
{
template <bool IsChild>
struct Select
{
typedef typename List::Type Type;
};
template <>
struct Select<false>
{
typedef typename FindChild<typename List::Next, Base>::Type Type;
};
typedef typename Select<IsDerivedFrom<Base, typename List::Type> >::Type Type;
};
当然q要对一些特D情况进行特化,例如NullType
template <class Base>
struct FindChild<NullType, Base>
{
typedef NullType Type;
};
q里使用NullType来表明没扑ֈ
实际操作Q?br>首先需要给3个Policy3个基c,分别?br>class AvgCrossPolicyBase{};
class SelectPolicyBase{};
class VariationPolicyBase{};
内容为空p了,q样也没有虚函数调用的开销
然后声明一个类来表C默认情况:
class DefaultPolicy{};
定义一个宏
#define TYPELIST_3_N(a, b, c) TypeList<a, TypeList<b, TypeList<c, NullType> > >
下面要写一些选择?用于把合适的cd选择出来Q如果没扑ֈQ则要用默认的cd
template <class List, class Base, class DefaultType>
struct Selector
{
template <class RetType>
struct Judge
{
typedef RetType Type;
};
template<>
struct Judge<NullType>
{
typedef DefaultType Type;
};
typedef typename Judge<typename FindChild<List, Base>::Type >::Type Type;
};
好啦Q现在整个类的声明可以写?/p>
template<class T
, class CrossPolicy_ = DefaultPolicy
, class SelectPolicy_ = DefaultPolicy
, class VariationPolicy_ = DefaultPolicy //其后的参数用户不可指?br> , class List = TYPELIST_3_N(CrossPolicy_, SelectPolicy_, VariationPolicy_)
, class CrossPolicy = typename Selector<List, CrossPolicyBase, AvgCrossPolicy>::Type
, class SelectPolicy = typename Selector<List, SelectPolicyBase, DefaultSelectPolicy>::Type
, class VariationPolicy = typename Selector<List, VariationPolicyBase, ReverseVariationPolicy>::Type
>
class Gene
: private CrossPolicy
, private SelectPolicy
, private VariationPolicy
{
....
};
其中W?-7个参敎ͼListQCrossPolicyQSelectPolicy和VariationPolicyQ是不由用户指定的,仅仅是ؓ了v一个别?br>W一个参数T必须指定Q然?Q?Q?q?个参数就可以L的改变顺序了
例如Q可以写Gene<T, DefaultSelectPolicy, AvgCrossPolicy>而不会有M问题
如果不想要最后面几个参数的话也行Q但是代码就要稍微长一?br>而且最好在c里面进?个typedef
typedef typename Selector<List, CrossPolicyBase, AvgCrossPolicy>::Type CrossPolicy;
{,以便在实现的时候?/p>
rule<> group, factor, term, exp;
group = '(' >> exp >> ')';
factor = int_p | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
exp = term >> *(('+' >> term) | ('-' >> term));
q里使用=代替::=, ?gt;>代替I格q接。ƈ且由于C++语法所限,EBNF中后|的*在spirit中改为前|?br>{式左边的单词被UCؓ一个ruleQ等式右边ؓrule的定义。我们可以看Z个group是一个exp加上一ҎP一个factor是一个整数或者一个group,一个term是一个或多个factor?/q接Q一个exp是一个或多个term?-q接。处于最端的exp可以据此识别Z下表辑ּ
12345
-12345
+12345
1 + 2
1 * 2
1/2 + 3/4
1 + 2 + 3 + 4
1 * 2 * 3 * 4
(1 + 2) * (3 + 4)
(-1 + 2) * (3 + -4)
1 + ((6 * 200) - 20) / 6
(1 + (2 + (3 + (4 + 5))))
得到一个rule之后Q我们就可以?/font> parse函数对一个串q行识别了。例?br>
该函数返回一个结构parse_infoQ可以通过讉K其中的full成员来判断是否成功识别,也可以访问stop成员来获知失败的位置。这里要特别提一点,关于各个W号之间的空|spirit的文档的正文说的是给parse再传一个参数space_pQ通知parse跌所有的I格Q然而在FAQ中又提到Q如果用以上方法定义ruleQ第三个参数传space_p会失败。原因是使用rule默认定义的规则被UCؓcharacter level parsingQ即字符U别解析Q而parse的第3个参C适用于phrase level parsingQ即语法U别解析。要使用W?个参数可以有几种Ҏ?br> 1。在parse的第二个参数直接传入一个EBNF表达式,不创建rule对象?br>
2。以rule<phrase_scanner_t>创徏rule?br>
注意虽然可以用这两个办法屏蔽I格Q但是这样可能完全改变EBNF文法的语义,其是在语言本n需要识别空格的时候。对于这U情况,可以不用第三个参数Qƈ在需要出现空格的地方加上space_p,或?space_p?space_pQ其??分别表示后面的符可l出Cơ以上和0ơ以上。例如一个以I格分隔的整数列表可以写成int_p >> *(+space_p >> int_p)
如上使用parse可以识别一个串Q但q不能做更多的操作,例如语法里的各个成分提取出来。对于这L需求,可以通过actor实现。下面是使用actor的一个简单例?br>
bool
parse_numbers(char const* str, vector<double>& v)
{
return parse(str,
// Begin grammar
(
real_p[push_back_a(v)] >> *(',' >> real_p[push_back_a(v)])
)
,
// End grammar
space_p).full;
}
注意?span class=identifier>real_p后面的[]Q中括号里面是一个仿函数Q函数指针或者函数对象)Q该仿函数具有如下调用型?br>
void operator()(IterT first, IterT last) const;
void operator()(NumT val) const;
void operator()(CharT ch) const;
一旦spase发现了匹?span class=identifier>real_p的子Ԍ׃调用该functor。不同的rule可能会对应不同的调用型别?/span>
W一个型别针对一般规则,first和lastZ个指向字W的q代器(一般ؓchar*Q?匚w的子串ؓ[first, last)
W二个型别针Ҏ字型规则Q如real_p和int_p, 参数val是一个数字类型?br>W三个性别针对单字W型规则Q如space_p, 参数ch是一个字W类型?br>real_p[push_back_a(v)]中的push_back_a是一个spirit已经定义好的functorQ它会将匚w好的内容依照匚w到的旉序调用v的push_back函数加入到v中?br>
到此spirit的常用功能就都介l完了。要详细深入了解可以参考spirit的文档?br>
最后在题一个注意要炏Vspirit的各UEBNFq接都是指针q接Q因此才能在expression被赋值前在group的定义里面用。所以在使用EBNF的时候一定要心不要局部变量的rule提供l全局或者类成员变量使用Q例如:
class A
{
rule<> s;
A()
{
rule<> r = int_p | hex_p;
s = r >> *(+space_p >> r); //error, r destructed after return
}
};
如果真想使用局部作用域Q可以在局部的rule前面加上static.