注:本文所使用编译器均为gcc4.1.2,即只能使用C++98标准。
背景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| struct Node { int mRow; int mCol; int mValue;
Node(int row, int col, int value) : mRow(row), mCol(col), mValue(value) {} };
class Iterator { public: virtual ~Iterator() {}
virtual Node GetValue() const = 0; virtual bool IsValid() const = 0; virtual void MoveNext() = 0; };
|
我们有一个接口类Iterator
,它有三个方法,可以返回Node
类型的值。现在我们要用这个接口来遍历一个矩阵。
矩阵的类型是vector<vector<int>>
,我们有几种不同的遍历需求:
1
| Iterator* NewRawIterator(const std::vector<std::vector<int> >& matrix);
|
这个方法返回一个最原始的Iterator
,只是单纯遍历数据。
1
| Iterator* NewOddIterator(const std::vector<std::vector<int> >& matrix);
|
在RawIterator
基础上,只返回奇数。
1
| Iterator* NewAppendPerRowIterator(const std::vector<std::vector<int> >& matrix, const std::vector<int>& appendix);
|
在OddIterator
基础上,遍历过程中,在每行末尾加上appendix
中的元素。
1
| Iterator* NewDoubleIterator(const std::vector<std::vector<int> >& matrix, const std::vector<int>& appendix);
|
在AppendPerRowIterator
基础上,返回的每个Node
的value都乘2。
测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int main(int argc, char** argv) { srand(time(NULL)); vector<vector<int> > matrix; vector<int> appendix; PrepareMatrix(matrix, 1000, 10000); PrepareAppendix(appendix, 3);
int64_t t0 = Measure(NewRawIterator(matrix)); int64_t t1 = Measure(NewOddIterator(matrix)); int64_t t2 = Measure(NewAppendPerRowIterator(matrix, appendix)); int64_t t3 = Measure(NewDoubleIterator(matrix, appendix));
cout << "Raw:" << t0 << endl << "Odd:" << t1 << endl << "Append:" << t2 << endl << "Double:" << t3 << endl; }
|
我们会在一个1000*10000的矩阵上测试各个Iterator
。
基于继承+组合的实现
上面的需求看起来很适合用继承+组合来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class RawIterator: public Iterator { public: explicit RawIterator(const vector<vector<int> >& matrix); };
class OddIterator: public Iterator { public: explicit OddIterator(Iterator* iter); };
class AppendPerRowIterator: public Iterator { public: AppendPerRowIterator(Iterator* iter, const vector<int>& appendix); };
class DoubleIterator: public Iterator { public: explicit DoubleIterator(Iterator* iter); };
|
我们用了4个派生自Iterator
的新类型来实现不同的需求,除了RawIterator
外,其它3种类型都是在组合了已有的Iterator
基础上实现新功能,因此可以任意顺序组合。
几个New函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Iterator* NewRawIterator(const vector<vector<int> >& matrix) { return new RawIterator(matrix); }
Iterator* NewOddIterator(const vector<vector<int> >& matrix) { Iterator* rawIter = NewRawIterator(matrix); return new OddIterator(rawIter); }
Iterator* NewAppendPerRowIterator(const vector<vector<int> >& matrix, const vector<int>& appendix) { Iterator* oddIter = NewOddIterator(matrix); return new AppendPerRowIterator(oddIter, appendix); }
Iterator* NewDoubleIterator(const vector<vector<int> >& matrix, const vector<int>& appendix) { Iterator* appendIter = NewAppendPerRowIterator(matrix, appendix); return new DoubleIterator(appendIter); }
|
测试结果为:
1 2 3 4
| Raw:164311 Odd:316020 Append:436596 Double:458733
|
可以看到,每增加一层功能,调用延时就明显上涨了一截。有没有其它性能更好的方案了呢?
mixin
mixin是给一组基础类,每个都建模了一个抽象概念,且能直接组合成一个新的类,像乐高一样,满足需求。如果你新增了基础类,只要它是和其它基础类正交的,就可以扩展到这个组合类里。
C++中的一种mixin方法是结合模板和继承,通过模板参数来组合基础类(见通过mixin组合功能)。
在上面这个例子中,RawIterator
是一个基础类,可以单独使用,而OddIterator
、AppendIterator
和DoubleIterator
则是用来提供新的功能的,是mixin类,我们可以将这三个类型实现为模板类,以OddIterator
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| template <typename T> class OddIterator: public T { public: OddIterator(?) : T(?) { MoveUntilOdd(); }
Node GetValue() const { return T::GetValue(); }
bool IsValid() const { return T::IsValid(); }
void MoveNext() { T::MoveNext(); MoveUntilOdd(); } private: void MoveUntilOdd() { while (T::IsValid() && T::GetValue().mValue % 2 == 0) { T::MoveNext(); } } };
|
可以看到,新的OddIterator
是通过继承自T
来给T
增加功能的,这有什么好处?摘取上文中的一段:
不需要堆分配。
没有运行期检查null。
不需要显式管理生命期。
没有多余的虚函数定义及其调用。
编译器有机会做更多优化。
似乎一切都很美好,除了代码有些怪异外。但我们还有一个问题没有解决:怎么构造T
?
一种mixin的通用构造方法
这是一个很大很复杂的课题,这里我只提出一种似乎可行的方式(至少本文的例子是没问题的)。
我们要求每个mixin类型,及需要与这个mixin类型组合的基础类型内部,都定义一个Context类型,且有一个只有一个Context类型参数的构造函数。例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class RawIterator: public Iterator { public: struct Context { const vector<vector<int> >& mMatrix;
explicit Context(const vector<vector<int> >& matrix) : mMatrix(matrix) {} };
explicit RawIterator(const Context& context); };
template <typename T> class OddIterator: public T { public: struct Context { const typename T::Context& mBase;
explicit Context(const typename T::Context& base) : mBase(base) {} };
OddIterator(const Context& c) : T(c.mBase) };
template <typename T> class AppendPerRowIterator: public T { public: struct Context { const typename T::Context& mBase; const vector<int>& mAppendix;
explicit Context(const typename T::Context& base, const vector<int>& appendix) : mBase(base), mAppendix(appendix) {} }; AppendPerRowIterator(const Context& c) : T(c.mBase)
|
通过这种方式,我们可以在不知道T
的具体类型(也就不知道它到底有什么样的构造函数)的情况下,构造出组合类型。从而也获得了任意组合这些mixin顺序的能力。
在C++11后,我们可以通过变长参数和继承构造函数的方式提供更优雅的mixin的通用构造方案,这是另外的话题,这里不展开了。
在使用了上面的方法来构造mixin后,几个New函数的实现为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| Iterator* NewRawIterator(const vector<vector<int> >& matrix) { RawIterator::Context c(matrix); return new RawIterator(c); }
Iterator* NewOddIterator(const vector<vector<int> >& matrix) { typedef OddIterator<RawIterator> OddIter;
RawIterator::Context c0(matrix); OddIter::Context c1(c0); return new OddIter(c1); }
Iterator* NewAppendPerRowIterator(const vector<vector<int> >& matrix, const vector<int>& appendix) { typedef OddIterator<RawIterator> OddIter; typedef AppendPerRowIterator<OddIter> AppendIter;
RawIterator::Context c0(matrix); OddIter::Context c1(c0); AppendIter::Context c2(c1, appendix); return new AppendIter(c2); }
Iterator* NewDoubleIterator(const vector<vector<int> >& matrix, const vector<int>& appendix) { typedef OddIterator<RawIterator> OddIter; typedef AppendPerRowIterator<OddIter> AppendIter; typedef DoubleIterator<AppendIter> DoubleIter;
RawIterator::Context c0(matrix); OddIter::Context c1(c0); AppendIter::Context c2(c1, appendix); DoubleIter::Context c3(c2); return new DoubleIter(c3); }
|
看起来还是很笨拙,但至少我们成功地把它们构造出来了。
mixin的性能
重新编译,运行,基于mixin的结果为:
1 2 3 4
| Raw:165505 Odd:164377 Append:187320 Double:191260
|
可以看到,在Raw结果不变的情况下(这是当然的),其它三种场景mixin方案的延时分别比继承+组合减少了48%、57%和58%,调用链越长,mixin方案的优势越大。
结论
本文提出了用mixin来解决功能组合的方案,且使用了Context类型来解决构造问题,相比常见的继承+组合的方案,mixin方案有着非常明显的性能优势。