C++ 的 idioms(一)
很早就想开始这部分了,但是一只都没下决心。最近实在是觉得自己这方面了解太少,还是催催自己多学习一点这方面的东西,这样写起来 code 不会太巍巍缩缩。这个系列的大部分内容来自这里。
operator=
切记一点,首先检查对方是不是自己。
class some {
public:
// ...
some&
operator= (const some& that) {
if (&that != this) {
// the following is what u should fill in
}
return *this ;
}
} ;
pImpl
这个大致是指 pointer to implementation:即一个类的实现不应该影响到依赖它接口的其他类的编译过程。试看下面的例子:
class complicated {
public:
// the exposed interfaces are listed here
private:
simple thing ;
} ;
比如这个代码出现在头文件 complicated.hpp 里面,那么有人要用这个类就会 include 之,这个时候就出现了个问题,尽管他只用 public 里面的函数,可是那天你改了 private 里面的实现,比如增加了一些东西,他就也得重新编译一遍:因为 complicated 的大小可能发生了变化,类似实例化 complicated object ; 时在 stack 上需要分配内存(complicated 的大小),这时就会出现问题,所以也必须重新编译。那怎么办?pImpl 就是说把这部分实现相关的东西放在一个内部的类里面,让 complicated 实现相关的成员独立出去,而在里面仅仅保存一个对应的指针。
class complicated {
public:
// the exposed interfaces are listed here
private:
struct pImpl ;
pImpl* ptr ;
} ;
这样我们就可以在别处写 class complicated::pImpl 的声明和实现了,无论怎么折腾实现部分都不会更改 complicated 的大小,这样就不会影响其他人的编译了。一个具体的例子是,Qt 是一个非常 heavy 应用 pImpl 技术的 GUI 库,试想今天 Qt 4.9 发布,明天某人提交了个 bug,后天开发人员修复更改了某处 implementation,我的 KDE 的程序全部是当时跟 Qt 4.9 连接上的,要是 Qt 没用 pImpl,所有动态链接到 Qt (相关类上)的 KDE 程序就都得重新编译一遍,要是是核心组件,那受影响的 KDE 程序的有多少啊…
那是不是这就是所有的了?嗯,很显然不是,pImpl 还有不少相关的 trick 呢!
- complicated::pImpl 理论上应该实现所有 complicated 的方法,而 complicated 直接 inline 调用 ptr -> 对应的版本;
- complicated::pImpl 其实没必要区分什么 public、protected 之类的东西,因为对它来说它已经被“封装”了,所以一般都写个 struct 了事(默认 public);
- 直接存指针太麻烦了,应该使用 smart pointer 保证对象消亡的时候也被释放,这就出现了一个问题,哪个类型的智能指针最合适?
- std::auto_ptr 能行么?如果仅仅一个 object 还行,对其复制就导致前一个的 complicated::pImpl 指针丢失了;
- boost::scoped_ptr 行么?注意它其实是 noncopyable 的,这到好,编译时如果你 copy complicated 对象就会报错的哦;
- boost::shared_ptr 行么?逻辑上复制了,但是两个 complicated 对象却会因此 share 同一个 complicated::pImpl 对象;
- 似乎 Loki 里面有个专门的 pImpl 的 smart pointer?
addressof
C++ 一个几家欢喜几家愁的 feature 就是允许 operator override,有人喜欢搞了个取址运算符重载,但是不返回 this 了,这下你怎么能获得这种 non-addressable 对象的地址呢?boost 提供了 addressof(见 boost/utility/addressof.hpp)。实现这个有两点需要注意。
第一,什么策略获得地址?自然是先转换成可以取地址的类型,然后将地址强制转换回原来的类型;注意某些可能有问题的修饰符,如 const、volatile 等,用 reinterpret_cast 的时候是不能扔掉他们的,但是一般加上的转换是可以的;然后通过 const_cast 去掉这些修饰,这样取地址再 reinterpret_cast 回去即可;
第二,我怎么知道输入的那个本身不是个指针?对指针 operator& 总是好用的,所以没必要真的取它的地址。那怎么区分这两个?template specification 或者函数重载!嗯,boost 自己就这么做的:
// in namespace boost::detail
template<class T>
struct addressof_impl {
static inline T*
f( T & v, long ) {
return reinterpret_cast<T*>(
&const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
}
static inline T*
f( T * v, int ) {
return v;
}
};
// ... helper function
template<class T> T * addressof( T & v ) {
return boost::detail::addressof_impl<T>::f( v, 0 );
}
rrdw 那个 int 和 long 的区别怎么回事?
——————
And they took Lot, Abram’s brother’s son, who dwelled in Sodom, and his goods, and departed.
这篇比较容易懂似乎…
hxtang
2012/02/18 at 4:42 AM
不要说废话,说点心得咩
zt
2012/02/18 at 5:25 PM
我觉得那个addressof里边的第一句话,你说“operator override” 我怎么觉得应该是“operator overload”呢?
xlzheng
2012/02/28 at 12:09 AM
你说的没错,应该是 overloading
zt
2012/02/28 at 12:40 AM