C++你不知道的那些事儿―C++语言的15个晦涩特性(二)
,一个slab分配器从单个的大内存块开始,使用placement new在块里顺序分配对象。这不仅避免了内存碎片,也节省了malloc引起的堆遍历的开销。
在声明变量的同时进行分支
C++包含一个语法缩写,能在声明变量的同时进行分支。看起来既像单个的变量声明也可以有if或while这样的分支条件。
struct Event { virtual ~Event() {} };
struct MouseEvent : Event { int x, y; };
struct KeyboardEvent : Event { int key; };
void log(Event *event) {
if (MouseEvent *mouse = dynamic_cast(event))
std::cout << "MouseEvent " << mouse->x << " " << mouse->y << std::endl;
else if (KeyboardEvent *keyboard = dynamic_cast(event))
std::cout << "KeyboardEvent " << keyboard->key << std::endl;
else
std::cout << "Event" << std::endl;
}
成员函数的引用修饰符
C++11允许成员函数在对象的值类型上进行重载,this指针会将该对象作为一个引用修饰符。引用修饰符会放在cv限定词(译者注:CV限定词有 三种:const限定符、volatile限定符和const-volatile限定符)相同的位置并依据this对象是左值还是右值影响重载解析:
#include
struct Foo {
void foo() & { std::cout << "lvalue" << std::endl; }
void foo() && { std::cout << "rvalue" << std::endl; }
};
int main() {
Foo foo;
foo.foo(); // Prints "lvalue"
Foo().foo(); // Prints "rvalue"
return 0;
}
图灵完备的模板元编程
C++模板是为了实现编译时元编程,也就是该程序能生成其它的程序。设计模板系统的初衷是进行简单的类型替换,但是在C++标准化过程中突然发现模板实际上功能十分强大,足以执行任意计算,虽然很笨拙很低效,但通过模板特化的确可以完成一些计算:
// Recursive template for general case
template
struct factorial {
enum { value = N * factorial::value };
};
// Template specialization for base case
template <>
struct factorial<0> {
enum { value = 1 };
};
enum { result = factorial<5>::value }; // 5 * 4 * 3 * 2 * 1 == 120
C++模板可以被认为是一种功能型编程语言,因为它们使用递归而非迭代而且包含不可变状态。你可以使用typedef创建一个任意类型的变量,使用enum创建一个int型变量,数据结构内嵌在类型自身。
// Compile-time list of integers
template
struct node {
enum { data = D };
typedef N next;
};
struct end {};
// Compile-time sum function
template
struct sum {
enum { value = L::data + sum::value };
};
template <>
struct sum {
enum { value = 0 };
};
// Data structures are embedded in types
typedef node<1, node<2, node<3, end> > > list123;
enum { total = sum::value }; // 1 + 2 + 3 == 6
当然这些例子没什么用,但模板元编程的确可以做一些有用的事情,比如可以操作类型列表。但是,使用C++模板的编程语言可用性极低,因此请谨慎和少量使用。模板代码很难
阅读,编译速度慢,而且因其冗长和迷惑的错误信息而难以调试。
指向成员的指针操作符
指向成员的指针操作符可以让你在一个类的任何实例上描述指向某个成员的指针。有两种pointer-to-member操作符,取值操作符*和指针操作符->:
#include
using namespace std;
struct Test {
int num;
void func() {}
};
// Notice the extra "Test::" in the pointer type
int Test::*ptr_num = &Test::num;
void (Test::*ptr_func)() = &Test::func;
int main() {
Test t;
Test *pt = new Test;
// Call the stored member function
(t.*ptr_func)();
(pt->*ptr_func)();
// Set the variable in the stored member slot
t.*ptr_num = 1;
pt->*ptr_num = 2;
delete pt;
return 0;
}
该特征实际上十分有用,尤其在写库的时候。例如,Boost::
Python, 一个用来将C++绑定到Python对象的库,就使用成员指针操作符,在包装对象时很容易的指向成员。
#include
#include
using namespace boost::python;
struct World {
std::string msg;
void greet() { std::cout << msg << std::endl; }
};
BOOST_PYTHON_MODULE(hello) {
class_("World")
.def_readwrite("msg", &World::msg)
.def("greet", &World::greet);
}
记住使用成员函数指针与普通函数指针是不同的。在成员函数指针和普通函数指针之间casting是无效的。例如,Microsoft编译器里的成员 函数使用了一个称为thiscall的优化调用约定,thiscall将this参数放到ecx寄存器里,而普通函数的调用约定却是在栈上解析所有的参 数。
而且,成员函数指针可能比普通指针大四倍左右,编译器需要存储函数体的地址,到正确父地址(多个继承)的偏移,虚函数表(虚继承)中另一个偏移的索引,甚至在对象自身内部的虚函数表的偏移也需要存储(为了前向声明类型)。
#include
struct A {};
struct B : virt