demonstrate 的 blog

daily blog

Archive for February 2012

C++ 的 idioms(十三)

leave a comment »

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.

Written by zt

2012/02/29 at 7:33 PM

C++ 的 idioms(十二)

leave a comment »

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(十一)

leave a comment »

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.

Written by zt

2012/02/28 at 12:02 AM

Follow

Get every new post delivered to your Inbox.