Archive for February 2012
C++ 的 idioms(十三)
virtual constructor
这个 idiom 的意思是说定义一个抽象类,定义两个接口用于返回该类的对象指针,如 create 与 clone 分别对应一般的构造函数和 copy constructor。子类对其进行实现,但是返回的是子类对象的指针。这样可行是因为所谓的 covariant return type,但是对智能指针来说,由于没有继承关系,所以并不能 wrap。
named loop
其实是个很 ws 的 macro,
#define named(blockname) goto blockname; \
blockname##_skip: if (0) \
blockname:
#define break(blockname) goto blockname##_skip
这样如下使用时由于有个 if (0) 就会跳过后面的循环。
named(foo)
for ( ... ) {
if (...)
break(foo) ;
}
nifty counter
当我们为某些库定义一些 static 对象的时候(比如 std::cout 等),需要确保这些对象能够正确的被初始化(调用构造函数)。如何放置这部分代码是比较有讲究的,因为用户可能在多个编译单元使用这些 static 对象。nifty counter 就是为了解决这个问题提出来的一种策略。首先我们需要在静态对象的类声明处做点手脚,埋伏另一个静态对象。
// in h/hpp file
class you_type_that_has_static_objs {
friend struct your_type_static_init ;
// ...
} ;
static struct your_type_static_init {
your_type_static_init () ;
~your_type_static_init () ;
} initializer ;
任何希望声明 your_type_that_has_static_objs 的静态对象时,必须 include 此头文件(不允许前向声明)。这样一来,只要声明或者使用静态对象 your_type_that_has_static_objs 都会在此之前声明了另外一个 your_type_static_init 的 static 对象。然后一般说来库会有一个编译单元实现这里的 your_type_static_init
// in cpp
static int nifty_counter ;
your_type_static_init::your_type_static_init () {
if (0 == ++ nifty_counter) {
// initialize static you_type_that_has_static_objs
}
}
your_type_static_init::~your_type_static_init () {
if (0 == nifty_counter --) {
// destruct you_type_that_has_static_objs
}
}
noncopyable
这个东西在 boost 里面也有(boost/noncopyable.hpp),
class noncopyable {
protected:
noncopyable() {}
~noncopyable() {}
private: // emphasize the following members are private
noncopyable( const noncopyable& );
const noncopyable& operator=( const noncopyable& );
};
比较有意思的是可以写个 CRTP 的版本,虽然没觉得比这个有多少用,一般通过 private 继承就行了。
non-virtual interface
这是一种 template method 的典型应用,template method 实现的接口,其中调用相关的虚函数,这样子类即便 override,在使用父类指针调用该接口时仍然保持原先的 template。
class base {
protected:
virtual void impl1 () ;
virtual void impl1 () ;
public:
void interface () {
impl1 () ;
impl2 () ;
}
} ;
class derived : public base {
protected:
virtual void impl1 () {
// implementation goes here
}
} ;
——————
And he said to him, I am the LORD that brought you out of Ur of the Chaldees, to give you this land to inherit it.
C++ 的 idioms(十二)
move constructor
C++11x 新增的一个所谓 rvalue reference 以及所谓 move constructor 就是来自 boost.move 的一些相关工作。当然在没有这个东西之前 std::auto_ptr 其实也够用,只是需要注意的是函数返回的临时 auto_ptr 是 const 的,会出现前面 const auto_ptr 类似的问题,但是实际上很多编译器似乎没理会这件事情,这被认为是 unsafe 的。
std::auto_ptr<int>
func_auto_ptr (int d) {
return std::auto_ptr<int> (new int (d)) ;
}
{
std::auto_ptr<int> e (func_auto_ptr (4)) ;
}
一种解决这个问题的策略的想法是通过一个 proxy 类自动的临时的获得 ownership,然后这时去掉函数内部构造的 smart pointer 的 ownership,然后自动的转换成为函数返回值,这仍然是 const 对象,在后面进行 copy construct 的时候(我们不提供 copy construct,这会迫使编译器只能使用 move construct)不成,只能通过 proxy 重新来做这件事情。
template <class T>
struct proxy {
T* t ;
proxy(T* a) : t(a) {
std::cout << "proxy()" << std::endl ;
}
~proxy () {
std::cout << "~proxy()" << std::endl ;
}
} ;
template <class T>
struct to_cast {
T* t ;
explicit to_cast (T* a) : t(a) {
std::cout << "constructor from normal pointer" << std::endl ;
}
to_cast (to_cast& that) {
t = that.t ;
that.t = NULL ;
std::cout << "move constructor" << std::endl ;
}
to_cast (proxy<T> that) : t (that.t) {
std::cout << "convert from proxy" << std::endl ;
}
~to_cast () {
delete t ;
std::cout << "destructor" << std::endl ;
}
operator proxy<T> () {
proxy<T> r (t) ;
t = NULL ;
std::cout << "convert to proxy" << std::endl ;
return r ;
}
} ;
这样可以保证在类似上面的调用情况下没有语义上的问题。
to_cast<int>
func (int d) {
return to_cast<int> (new int (d)) ;
}
{
to_cast<int> d (func (3)) ;
}
运行结果如下
constructor from normal pointer proxy() convert to proxy convert from proxy ~proxy() destructor proxy() convert to proxy convert from proxy ~proxy() destructor destructor
multi-statement macro
需要写多行宏的时候常会因为最后用户多加一个 trailing semicolon 而出现错误,比较好的方式是把宏仍然伪装成类似某个语句的样子,常见的伪装方式如下
#define MACRO(arg1, arg2) do { \
/* declarations, if any */ \
statement1; \
statement2; \
/* ... */ \
} while(0) /* (no trailing ; ) */
通过这个 while (0) 和最后的分号连接,编译器一般能够优化处理掉这种没有循环的循环。
member detector
这是 SFINAE (替换失败并不是编译错误)的应用之一,检测某个类是否含有某些成员、成员函数,或者存在静态函数等。
#define CREATE_MEM_DETECTOR(x) \
template<typename T> \
class detect_##x { \
struct fallback { int x; } ; \
struct derived : public T, public fallback { } ; \
template<typename U, U> struct check ; \
typedef char no [1]; \
typedef char yes [2]; \
template<typename U> \
static no & func (check<int fallback::*, &U::x> *); \
template<typename U> \
static yes & func(...); \
public: \
typedef detect_##x type; \
enum { value = sizeof (func<derived> (0)) == sizeof (yes) }; \
}
struct foo {
int y ;
} ;
CREATE_MEM_DETECTOR (x) ;
CREATE_MEM_DETECTOR (y) ;
// usee it like this
if (detect_x<foo>::value) {
// ...
}
named constructor
这个其实相对容易,我们可以通过不同名字的 factory function 来产生对应的 object。对应的函数都是 static 的。具体的例子见这里。
named parameter
这方面的工作怕是就是 boost.parameter 的“专利”了,能搞得跟 python 一样写函数,虽然一方面方便了用函数的人,也在另外的一方面损害了写函数的人 -,-。比起其他的 idiom 这个算是非常 ugly 的东西了吧,不到万不得已不要用…
named template parameter
这个技术是为了方便写多个模板参数。这部分内容来自这里。核心思想在于多个 policy 进行组合的时候,可以用继承的时候对父类 typedef 进行 override,这样获得的子类同样的 typedef 就能拿到不同的 policy 了。
struct default_policies {
typedef default_quack_policy quack_policy ;
typedef default_fly_policy fly_policy ;
// many more policies ...
} ;
template <Policy>
struct set_quack : virtual public default_policies {
typedef Policy quack_policy ;
} ;
// other policy setter...
似乎这样一来,我们就可以写个下面的东西
template <class setter1, class setter2, ...>
struct policy_selector : public setter1, public setter2, ... {
} ;
但是默认情况下传入的 setter 比如有同名的,比如都是 default_policies 就会出现问题。为此我们绕个弯子,
template <class base, int id>
struct discriminator : public base {} ;
template <class setter1, class setter2, ...>
struct policy_selector
: public discriminator<setter1, 1>, public discriminator<setter2, 2>, ... {
} ;
最终用户使用的时候用法类似下面的:
duck<> my_default_duck ; duck<set_quack<sound_policy>, set_fly<no_fly>, ...> my_super_duck ;
——————
And he believed in the LORD; and he counted it to him for righteousness.
C++ 的 idioms(十一)
inner class
如果存在两个“无关”(继承关系)的接口同名(很遗憾而且没有 virtual 的 destructor),而又需要实现两者的一个“结合体”,最好的方式不是通过继承。因为这个接口的意义会非常含混,别人必须读文档才能理解这个同名接口的表意,另一方面由于 non-virtual 的 destructor 需要避免增加成员。inner class 就是为了解决这个问题而提出的 idiom,例子来自 wikibooks,
class base1 {
virtual int open (int) {}
} ;
class base2 {
virtual int open (int) {}
} ;
class derived {
class base1_imp : public base1 {
derived *p ;
public:
base1_imp (derived* q) : p (q) {}
virtual int open (int j) {
return p -> base1_open (j) ;
}
} base1_obj ;
class base2_imp : public base2 {
derived *p ;
public:
base2_imp (derived* q) : p (q) {}
virtual int open (int j) {
return p -> base2_open (j) ;
}
} base2_obj ;
friend class base1_imp ;
friend class base2_imp ;
int base1_open (int j) {}
int base2_open (int j) {}
public:
operator base1& () {return base1_obj ;}
operator base2& () {return base2_obj ;}
} ;
int
base1_open (base1& o, int j) {
return o.open (j) ;
}
int
base2_open (base2& o, int j) {
return o.open (j) ;
}
核心的 idea 是两个类的接口对应两个 inner class,这两个 inner class 实际上是个 proxy,他们将对应的 open 传递到 “derived” 类对应的具体实现里面,这样 derived 可以决定如何对两种情况进行封装和处理,或者引用对应类的实现。而用户使用的时候,需要分别用外部不同的两个函数进行调用以示区分,这里通过定义 conversion operator 将 inner class 对象传递出去就能得到需要的效果了。
int2type
这个是 boost.MPL 的惯用手法了
template <int I> struct integral_constant {
enum {
value = I ;
} ;
} ;
interface class
嗯,Java 里面已经非常常见了,C++ 里面其实也挺常见的。定义一个抽象类,里面都是 pure virtual。(见 wikibooks 相关文章了)
iterator pair
其实和 coercion by member template 类似,容器初始化应该允许使用 iterator pair 来表示 copy 的 range,因此一般会提供一个 template 干这个事情。
template <class T, ...>
class some_container {
public:
template <class Iterator>
some_container (Iterator begin, Iterator end) {
// copying
}
} ;
making new friends
这个主要是如何为 template 引入 friend,
// Forward declarations
template<class T> class Foo;
template<class T> ostream& operator<<(ostream&,
const Foo<T>&);
template<class T> class Foo {
T val;
public:
Foo(const T& t) { val = t; }
friend ostream& operator<< <>(ostream&, const Foo<T>&);
};
template<class T>
ostream& operator<<(ostream& os, const foo<T>& b) {
return os << b.value;
}
需要注意很容易出错的那种写法产生错误居然是链接错误,换言之流插入甚至没有被实例化。这个原因到底是啥呢?
metafunction
这个是 metaprogramming 的精髓,前面已经有讨论,就是某些 typedef 了 type 的 struct,通过他们我们可以进行类型的运算。
——————
And he brought him forth abroad, and said, Look now toward heaven, and tell the stars, if you be able to number them: and he said to him, So shall your seed be.