C++你不知道的那些事儿―C++语言的15个晦涩特性(四)
ual A {};
struct C {};
struct D : A, C {};
struct E;
int main() {
std::cout << sizeof(void (A::*)()) << std::endl;
std::cout << sizeof(void (B::*)()) << std::endl;
std::cout << sizeof(void (D::*)()) << std::endl;
std::cout << sizeof(void (E::*)()) << std::endl;
return 0;
}
// 32-bit Visual C++ 2008: A = 4, B = 8, D = 12, E = 16
// 32-bit GCC 4.2.1: A = 8, B = 8, D = 8, E = 8
// 32-bit Digital Mars C++: A = 4, B = 4, D = 4, E = 4
在Digital Mars编译器里所有的成员函数都是相同的大小,这是源于这样一个聪明的设计:生成“thunk”函数来运用右偏移而不是存储指针自身内部的偏移。
静态实例方法
C++中可以通过实例调用静态方法也可以通过类直接调用。这可以使你不需要更新任何调用点就可以将实例方法修改为静态方法。
struct Foo {
static void foo() {}
};
// These are equivalent
Foo::foo();
Foo().foo();
重载++和–
C++的设计中自定义操作符的函数名称就是操作符本身,这在大部分情况下都工作的很好。例如,一元操作符的-和二元操作符的-(取反和相减)可以通 过参数个数来区分。但这对于一元递增和递减操作符却不奏效,因为它们的特征似乎完全相同。C++语言有一个很笨拙的技巧来解决这个问题:后缀++和–操作 符必须有一个空的int参数作为标记让编译器知道要进行后缀操作(是的,只有int类型有效)。
struct Number {
Number &operator ++ (); // Generate a prefix ++ operator
Number operator ++ (int); // Generate a postfix ++ operator
};
操作符重载和检查顺序
重载,(逗号),||或者&&操作符会引起混乱,因为它打破了正常的检查规则。通常情况下,逗号操作符在整个左边检查完毕才开始检 查右边,|| 和 &&操作符有短路行为:仅在必要时才会去检查右边。无论如何,操作符的重载版本仅仅是函数调用且函数调用以未指定的顺序检查它们的参数。
重载这些操作符只是一种滥用C++语法的方式。作为一个实例,下面我给出一个Python形式的无括号版打印语句的C++实现:
#include
namespace __hidden__ {
struct print {
bool space;
print() : space(false) {}
~print() { std::cout << std::endl; }
template
print &operator , (const T &t) {
if (space) std::cout << ' ';
else space = true;
std::cout << t;
return *this;
}
};
}
#define print __hidden__::print(),
int main() {
int a = 1, b = 2;
print "this is a test";
print "the sum of", a, "and", b, "is", a + b;
return 0;
}
函数作为模板参数
众所周知,模板参数可以是特定的整数也可以是特定的函数。这使得编译器在实例化模板代码时内联调用特定的函数以获得更高效的执行。下面的例子里,函数memoize的模板参数也是一个函数且只有新的参数值才通过函数调用(旧的参数值可以通过cache获得):
#include
template
int memoize(int x) {
static std::map cache;
std::map::iterator y = cache.find(x);
if (y != cache.end()) return y->second;
return cache[x] = f(x);
}
int fib(int n) {
if (n < 2) return n;
return memoize(n - 1) + memoize(n - 2);
}
模板的参数也是模板
模板参数实际上自身的参数也可以是模板,这可以让你在实例化一个模板时可以不用模板参数就能够传递模板类型。看下面的代码:
template
struct Cache { ... };
template
struct NetworkStore { ... };
template
struct MemoryStore { ... };
template
struct CachedStore {
Store store;
Cache cache;
};
CachedStore, int> a;
CachedStore, int> b;
CachedStore的cache存储的数据类型与store的类型相同。然而我们在实例化一个CachedStore必须重复写数据类型(上面 的代码是int型),store本身要写,CachedStore也要写,关键是我们这并不能保证两者的数据类型是一致的。我们真的只想要确定数据类型一 次即可,所以我们可以强制其不变,但是没有类型参数的列表会引起编译出错:
// 下面编译通不过,因为NetworkStore和MemoryStore缺失类型参数
CachedStore c;
CachedStore d;
模板的模板参数可以让我们获得想要的语法。注意你必须使用class关键字作为模板参数(他们自身的参数也是模板)
template class Store, typename T>
struct CachedStore2 {
Store store;
Cache cache;
};
CachedStore2 e;
CachedStore2 f;
try块作为函数
函数的try块会在检查构造函数的初始化列表时捕获抛出的异常。你不能在初始化列表的周围加上try-catch块,因为其只能出现在函数体外。为了解决这个问题,C++允许try-catch块也可作为函数体:
int f() { throw 0; }
// 这里没有办法捕获由f()抛出的异常
struct A {
int a;
A::A() : a(f()) {}
};
// 如果try-catch块被用作函数体并且初始化列表移至try关键字之后的话,
// 那么由f()抛出的异常就可以捕获到
struct B {
int b;
B::B() try : b(f()) {
} c