设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

C++ 语言的 15 个晦涩特性

2013-12-30 14:57| 发布者: 红黑魂| 查看: 3433| 评论: 0|来自: 伯乐在线

摘要: 这个列表收集了 C++ 语言的一些晦涩(Obscure)特性,是我经年累月研究这门语言的各个方面收集起来的。C++非常庞大,我总是能学到一些新知识。即使你对C++已了如指掌,也希望你能从列表中学到一些东西。下面列举的特 ...

指向成员的指针操作符

指向成员的指针操作符可以让你在一个类的任何实例上描述指向某个成员的指针。有两种pointer-to-member操作符,取值操作符*和指针操作符->:

01#include <iostream>
02using namespace std;
03 
04struct Test {
05  int num;
06  void func() {}
07};
08 
09// Notice the extra "Test::" in the pointer type
10int Test::*ptr_num = &Test::num;
11void (Test::*ptr_func)() = &Test::func;
12 
13int main() {
14  Test t;
15  Test *pt = new Test;
16 
17  // Call the stored member function
18  (t.*ptr_func)();
19  (pt->*ptr_func)();
20 
21  // Set the variable in the stored member slot
22  t.*ptr_num = 1;
23  pt->*ptr_num = 2;
24 
25  delete pt;
26  return 0;
27}

该特征实际上十分有用,尤其在写库的时候。例如,Boost::Python, 一个用来将C++绑定到Python对象的库,就使用成员指针操作符,在包装对象时很容易的指向成员。

01#include <iostream>
02#include <boost/python.hpp>
03using namespace boost::python;
04 
05struct World {
06  std::string msg;
07  void greet() { std::cout << msg << std::endl; }
08};
09 
10BOOST_PYTHON_MODULE(hello) {
11  class_<World>("World")
12    .def_readwrite("msg", &World::msg)
13    .def("greet", &World::greet);
14}

记住使用成员函数指针与普通函数指针是不同的。在成员函数指针和普通函数指针之间casting是无效的。例如,Microsoft编译器里的成员函数使用了一个称为thiscall的优化调用约定,thiscall将this参数放到ecx寄存器里,而普通函数的调用约定却是在栈上解析所有的参数。

而且,成员函数指针可能比普通指针大四倍左右,编译器需要存储函数体的地址,到正确父地址(多个继承)的偏移,虚函数表(虚继承)中另一个偏移的索引,甚至在对象自身内部的虚函数表的偏移也需要存储(为了前向声明类型)。

01#include <iostream>
02 
03struct A {};
04struct B : virtual A {};
05struct C {};
06struct D : A, C {};
07struct E;
08 
09int main() {
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;
14  return 0;
15}
16 
17// 32-bit Visual C++ 2008:  A = 4, B = 8, D = 12, E = 16
18// 32-bit GCC 4.2.1:        A = 8, B = 8, D = 8,  E = 8
19// 32-bit Digital Mars C++: A = 4, B = 4, D = 4,  E = 4

在Digital Mars编译器里所有的成员函数都是相同的大小,这是源于这样一个聪明的设计:生成“thunk”函数来运用右偏移而不是存储指针自身内部的偏移。

 

静态实例方法

C++中可以通过实例调用静态方法也可以通过类直接调用。这可以使你不需要更新任何调用点就可以将实例方法修改为静态方法。

1struct Foo {
2  static void foo() {}
3};
4 
5// These are equivalent
6Foo::foo();
7Foo().foo();

 

重载++和&#8211;

C++的设计中自定义操作符的函数名称就是操作符本身,这在大部分情况下都工作的很好。例如,一元操作符的-和二元操作符的-(取反和相减)可以通过参数个数来区分。但这对于一元递增和递减操作符却不奏效,因为它们的特征似乎完全相同。C++语言有一个很笨拙的技巧来解决这个问题:后缀++和&#8211;操作符必须有一个空的int参数作为标记让编译器知道要进行后缀操作(是的,只有int类型有效)。

1struct Number {
2  Number &operator ++ (); // Generate a prefix ++ operator
3  Number operator ++ (int); // Generate a postfix ++ operator
4};

 

操作符重载和检查顺序

重载,(逗号),||或者&&操作符会引起混乱,因为它打破了正常的检查规则。通常情况下,逗号操作符在整个左边检查完毕才开始检查右边,|| 和 &&操作符有短路行为:仅在必要时才会去检查右边。无论如何,操作符的重载版本仅仅是函数调用且函数调用以未指定的顺序检查它们的参数。

重载这些操作符只是一种滥用C++语法的方式。作为一个实例,下面我给出一个Python形式的无括号版打印语句的C++实现:

01#include <iostream>
02 
03namespace __hidden__ {
04  struct print {
05    bool space;
06    print() : space(false) {}
07    ~print() { std::cout << std::endl; }
08 
09    template <typename T>
10    print &operator , (const T &t) {
11      if (space) std::cout << &#039; &#039;;
12      else space = true;
13      std::cout << t;
14      return *this;
15    }
16  };
17}
18 
19#define print __hidden__::print(),
20 
21int main() {
22  int a = 1, b = 2;
23  print "this is a test";
24  print "the sum of", a, "and", b, "is", a + b;
25  return 0;
26}

 

函数作为模板参数

众所周知,模板参数可以是特定的整数也可以是特定的函数。这使得编译器在实例化模板代码时内联调用特定的函数以获得更高效的执行。下面的例子里,函数memoize的模板参数也是一个函数且只有新的参数值才通过函数调用(旧的参数值可以通过cache获得):

01#include <map>
02 
03template <int (*f)(int)>
04int memoize(int x) {
05  static std::map<intint> cache;
06  std::map<intint>::iterator y = cache.find(x);
07  if (y != cache.end()) return y->second;
08  return cache[x] = f(x);
09}
10 
11int fib(int n) {
12  if (n < 2) return n;
13  return memoize<fib>(n - 1) + memoize<fib>(n - 2);
14}

 

模板的参数也是模板

模板参数实际上自身的参数也可以是模板,这可以让你在实例化一个模板时可以不用模板参数就能够传递模板类型。看下面的代码:

01template <typename T>
02struct Cache { ... };
03 
04template <typename T>
05struct NetworkStore { ... };
06 
07template <typename T>
08struct MemoryStore { ... };
09 
10template <typename Store, typename T>
11struct CachedStore {
12  Store store;
13  Cache<T> cache;
14};
15 
16CachedStore<NetworkStore<int>, int> a;
17CachedStore<MemoryStore<int>, int> b;

CachedStore的cache存储的数据类型与store的类型相同。然而我们在实例化一个CachedStore必须重复写数据类型(上面的代码是int型),store本身要写,CachedStore也要写,关键是我们这并不能保证两者的数据类型是一致的。我们真的只想要确定数据类型一次即可,所以我们可以强制其不变,但是没有类型参数的列表会引起编译出错:

1// 下面编译通不过,因为NetworkStore和MemoryStore缺失类型参数
2CachedStore<NetworkStore, int> c;
3CachedStore<MemoryStore, int> d;

模板的模板参数可以让我们获得想要的语法。注意你必须使用class关键字作为模板参数(他们自身的参数也是模板)

1template <template <typenameclass Store, typename T>
2struct CachedStore2 {
3  Store<T> store;
4  Cache<T> cache;
5};
6 
7CachedStore2<NetworkStore, int> e;
8CachedStore2<MemoryStore, int> f;

 

try块作为函数

函数的try块会在检查构造函数的初始化列表时捕获抛出的异常。你不能在初始化列表的周围加上try-catch块,因为其只能出现在函数体外。为了解决这个问题,C++允许try-catch块也可作为函数体:

01int f() { throw 0; }
02 
03// 这里没有办法捕获由f()抛出的异常
04struct A {
05  int a;
06  A::A() : a(f()) {}
07};
08 
09// 如果try-catch块被用作函数体并且初始化列表移至try关键字之后的话,
10// 那么由f()抛出的异常就可以捕获到
11struct B {
12  int b;
13  B::B() try : b(f()) {
14  catch(int e) {
15  }
16};

奇怪的是,这种语法不仅仅局限于构造函数,也可用于其他的所有函数定义。

原文链接: Evan Wallace   翻译: 伯乐在线 敏敏
译文链接: http://blog.jobbole.com/54140/


酷毙

雷人
1

鲜花

鸡蛋

漂亮

刚表态过的朋友 (1 人)

  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部