指向成员的指针操作符 指向成员的指针操作符可以让你在一个类的任何实例上描述指向某个成员的指针。有两种pointer-to-member操作符,取值操作符*和指针操作符->: 10 | int Test::*ptr_num = &Test::num; |
11 | void (Test::*ptr_func)() = &Test::func; |
该特征实际上十分有用,尤其在写库的时候。例如,Boost::Python, 一个用来将C++绑定到Python对象的库,就使用成员指针操作符,在包装对象时很容易的指向成员。 02 | #include <boost/python.hpp> |
03 | using namespace boost::python; |
07 | void greet() { std::cout << msg << std::endl; } |
10 | BOOST_PYTHON_MODULE(hello) { |
11 | class_<World>( "World" ) |
12 | .def_readwrite( "msg" , &World::msg) |
13 | .def( "greet" , &World::greet); |
记住使用成员函数指针与普通函数指针是不同的。在成员函数指针和普通函数指针之间casting是无效的。例如,Microsoft编译器里的成员函数使用了一个称为thiscall的优化调用约定,thiscall将this参数放到ecx寄存器里,而普通函数的调用约定却是在栈上解析所有的参数。 而且,成员函数指针可能比普通指针大四倍左右,编译器需要存储函数体的地址,到正确父地址(多个继承)的偏移,虚函数表(虚继承)中另一个偏移的索引,甚至在对象自身内部的虚函数表的偏移也需要存储(为了前向声明类型)。 04 | struct B : virtual A {}; |
10 | std::cout << sizeof ( void (A::*)()) << std::endl; |
11 | std::cout << sizeof ( void (B::*)()) << std::endl; |
12 | std::cout << sizeof ( void (D::*)()) << std::endl; |
13 | std::cout << sizeof ( void (E::*)()) << std::endl; |
在Digital Mars编译器里所有的成员函数都是相同的大小,这是源于这样一个聪明的设计:生成“thunk”函数来运用右偏移而不是存储指针自身内部的偏移。 静态实例方法C++中可以通过实例调用静态方法也可以通过类直接调用。这可以使你不需要更新任何调用点就可以将实例方法修改为静态方法。 重载++和–C++的设计中自定义操作符的函数名称就是操作符本身,这在大部分情况下都工作的很好。例如,一元操作符的-和二元操作符的-(取反和相减)可以通过参数个数来区分。但这对于一元递增和递减操作符却不奏效,因为它们的特征似乎完全相同。C++语言有一个很笨拙的技巧来解决这个问题:后缀++和–操作符必须有一个空的int参数作为标记让编译器知道要进行后缀操作(是的,只有int类型有效)。 2 | Number &operator ++ (); |
3 | Number operator ++ ( int ); |
操作符重载和检查顺序重载,(逗号),||或者&&操作符会引起混乱,因为它打破了正常的检查规则。通常情况下,逗号操作符在整个左边检查完毕才开始检查右边,|| 和 &&操作符有短路行为:仅在必要时才会去检查右边。无论如何,操作符的重载版本仅仅是函数调用且函数调用以未指定的顺序检查它们的参数。 重载这些操作符只是一种滥用C++语法的方式。作为一个实例,下面我给出一个Python形式的无括号版打印语句的C++实现: 06 | print() : space( false ) {} |
07 | ~print() { std::cout << std::endl; } |
10 | print &operator , ( const T &t) { |
11 | if (space) std::cout << ' '; |
19 | #define print __hidden__::print(), |
23 | print "this is a test" ; |
24 | print "the sum of" , a, "and" , b, "is" , a + b; |
函数作为模板参数众所周知,模板参数可以是特定的整数也可以是特定的函数。这使得编译器在实例化模板代码时内联调用特定的函数以获得更高效的执行。下面的例子里,函数memoize的模板参数也是一个函数且只有新的参数值才通过函数调用(旧的参数值可以通过cache获得): 03 | template < int (*f)( int )> |
05 | static std::map< int , int > cache; |
06 | std::map< int , int >::iterator y = cache.find(x); |
07 | if (y != cache.end()) return y->second; |
08 | return cache[x] = f(x); |
13 | return memoize<fib>(n - 1) + memoize<fib>(n - 2); |
模板的参数也是模板模板参数实际上自身的参数也可以是模板,这可以让你在实例化一个模板时可以不用模板参数就能够传递模板类型。看下面的代码: 05 | struct NetworkStore { ... }; |
08 | struct MemoryStore { ... }; |
10 | template < typename Store, typename T> |
16 | CachedStore<NetworkStore< int >, int > a; |
17 | CachedStore<MemoryStore< int >, int > b; |
CachedStore的cache存储的数据类型与store的类型相同。然而我们在实例化一个CachedStore必须重复写数据类型(上面的代码是int型),store本身要写,CachedStore也要写,关键是我们这并不能保证两者的数据类型是一致的。我们真的只想要确定数据类型一次即可,所以我们可以强制其不变,但是没有类型参数的列表会引起编译出错: 2 | CachedStore<NetworkStore, int > c; |
3 | CachedStore<MemoryStore, int > d; |
模板的模板参数可以让我们获得想要的语法。注意你必须使用class关键字作为模板参数(他们自身的参数也是模板) 1 | template < template < typename > class Store, typename T> |
7 | CachedStore2<NetworkStore, int > e; |
8 | CachedStore2<MemoryStore, int > f; |
try块作为函数函数的try块会在检查构造函数的初始化列表时捕获抛出的异常。你不能在初始化列表的周围加上try-catch块,因为其只能出现在函数体外。为了解决这个问题,C++允许try-catch块也可作为函数体: 奇怪的是,这种语法不仅仅局限于构造函数,也可用于其他的所有函数定义。 原文链接: Evan Wallace 翻译: 伯乐在线 - 敏敏 译文链接: http://blog.jobbole.com/54140/ |