设计哲学:职责单一还是接口单一
今天跟康神讨论设计,他提到一个设计哲学(这个词就很让人崇拜),很是有道理。大概意思是说,在设计中,有人喜欢职责单一,所有东西分的很细,有人喜欢接口单一,保证接口稳定,这是个设计哲学问题,各有优缺。
我自己是一个极度喜欢保持职责单一的人,面对康神设计的接口单一但内部逻辑较复杂的模块,多少有点想重构的冲动。其实这两种设计哲学都没太大问题,确实得看需求来确定到底用什么。
接口单一的设计,最典型例子的就是大家都知道的printf,这个东西博大精深,可以组合打印任意格式的文字,如果C标准委员会乐意,还可以通过扩展格式字符串来实现更多的打印选项,同时保证100%向下兼容。还有很多接口,它们通过传递一个结构来传递参数,这个结构拥有众多成员,每次调用接口时只用其中一部分。这些接口也能像printf一样易扩展,康神设计的就是后面这种接口。
这样设计的好处是保持整个系统接口的统一和稳定,这对于一个大型工业系统来说至关重要,因为牵一发动全身,改变任何一个通用接口都会付出很大代价。但它的缺点也十分明显,主要体现是效率不高和模块职责不明。
可以想象,printf的运行效率并不高,对于%以及后面参数的解析是在运行时完成的,每次运行都会在解析格式字符串上花时间。传结构的缺点是内存使用效率不高,并且随着业务逻辑不断变复杂,结构会大到不能接受的地步,最终肯定在结构中设置一个类似于格式字符串或者表达式的东西,逐渐转变成printf的设计模式。
同时,由于接口过于强大,在接口背后隐藏的逻辑也必定复杂无比。想想解析格式字符串或解析结构时该需要多少烦人的if…else,就知道要实现这种设计将会写出多少逻辑复杂的代码,会产生多少的bad smell,给后来维护的人造成不少困扰,甚至会引入不少潜在的错误。
反观职责单一的设计,没有接口单一设计的缺点,但劣势也很明显,就是没有统一的接口,使得整个系统模块之间的调用方式始终难以稳定。如果整个系统由一个人开发还好,如果多人协作,则会大大降低开发效率,会将大量时间用在沟通接口和解决联调问题上面。公平的说,职责单一的设计也可以用facade模式来提供统一接口,但是facade模式的实现代码又会变得和接口统一时一样,没有根本改变,只是一个折中和权衡。
那是否说明“完美”的职责单一的设计不适合大型开发呢?也不一定,因为我们有C++和无比变态的模板技巧。
比如我们可以设计出一个这样的print,功能类似于printf。注意,这个函数设计成只在print的时候才输出,expression类不会造成任何输出。
pattern p = builder() << “uid: ” << uid << ” uname: ” << name;
uid = 1000;
name = “realdodo”;
print(p); // 打印出”uid: 1000 uname: realdodo”
如果熟悉boost.spirit的朋友肯定觉得这个代码很眼熟,没办法,谁要我是这个库的忠实粉丝呢。在这里,pattern_builder类只是一个模板生成器,而不进行任何计算(不同于sstream这样的类),它用<<运算符针对各种类型生成各种特化的模板类,类似于一个嵌套的pair<>。这些模板类层层嵌套,但是pattern类可以轻松搞定,方法就是设计一个concrete_pattern类。
struct concrete_pattern_base
{
concrete_pattern_base() {}
virtual ~concrete_pattern_base() {}
virtual string to_string() const = 0;
};
tempate <typename builder>
struct concrete_pattern : public concrete_pattern_base
{
builder builder_;
concrete_pattern(const builder & b)
: builder_(b)
{}
virtual string to_string() const
{
// 用模板特化的方法实现to_string()方法,这里略去
}
};
这样,pattern的实现就非常简单。
class pattern
{
concrete_pattern_base * pattern_;
public:
template <typename builder>
pattern(const builder [...]
什么是“Nabialek trick”?
现在是时候了,经过这些天的学习和尝试,我终于知道boost.spirit里谈到的Nabialek trick是什么东西了,当然应该大方的与大家分享。
这个Nabialek trick其实并不是编译期间的技巧,而是利用查表的方式将一些静态的类型推导变成简单的运行时调用,用性能损失换取编译期间的复杂,是一种相反的过程。可以说,Nabialek trick给人的感觉是完完全全的没有意义,要知道C++的template就是为了提升运行时性能而增加编译期间运算,这个反其道而行意义何在?其实说白了,意义就在于:消除模板技巧带来的疯狂的自动类型推导,以及解除对某些类型的静态绑定。前者意义在于可以提升编译速度,而且也可以避免达到模板嵌套上限(比如MSVC 7.1,最多只能嵌套256层,这已经算不错了),后者意义在于可以让复杂的表达式变得更容易匹配、更快的执行和更加灵活(所谓的将“线性非决定式”转化为“决定式”,将“或”逻辑从表达式中剔除,避免不必要回溯以加快速度),但是由于创建查找表的过程涉及到内存分配,所以后者并不能总是达到很好效果。
我们再从正面来想想Expression Templates给编程带来的困难,请看下面的示例代码(来自boost.spirit的帮助/libs/spirit/doc/techniques.html,我做了一些简化和修改):
r =
“//” >> *(anychar_p – ‘n’) >> ‘n’
| “/*” >> *(anychar_p – “*/”) >> “*/”
;
在这里,r的类型就是:
alternative<sequence<sequence<strlit<const char*>, kleene_star<difference<anychar_parser, chlit<char> > > >, chlit<char> >, sequence<sequence<
strlit<const char*>, kleene_star<difference<anychar_parser, strlit<const char*> > > >, strlit<const char*> > >
是的,我没指望任何人能够看懂……不过假如做一下的赋值:
line1 = “//” >> *(anychar_p – ‘n’) >> ‘n’;
line2 = “/*” >> *(anychar_p – “*/”) >> “*/”;
r = line1 | line2;
这样的代码应该好理解一些,但是没有本质不同。再来改改:
line1 [...]
boost中如何面对疯狂的类型推导?
这些天,我一直在说一个名词:Expression Templates。这个东西很有用,我也会在未来把这篇英文文档用易懂的中文讲解一遍(当然,各位已经看过的高手可以无视我未来的这篇文章)。
可以很肯定地说,Expression Templates一定是未来C++各种类库设计的标准思路,现在boost.spirit、boost.lambda、boost.uBLAS、boost.xpressive等库都是用这种思路设计,而且介绍这种方法的论文也明确指出,合理应用这种方法可以显著的提高程序性能,能让C++程序库的效率高于相同功能的C程序库(例如std::sort和qsort的比较)。但是它的一个致命缺点是:令人发狂的类型推导。
看看下面这个例子吧,它是boost.spirit的一个例子(boost 1.34.0的libs/spirit/example/techniques/no_rules/no_rule2.cpp),用于说明如果没有自动类型推导或者类似功能的机制,类型定义会变得多么的疯狂:
程序代码
struct skip_grammar : grammar<skip_grammar>
{
template <typename ScannerT>
struct definition
{
definition(skip_grammar const& /*self*/)
: skip
( space_p
| ”//” >> *(anychar_p - ’\n’) >> ’\n’
| ”/*” >> *(anychar_p - ”*/”) >> ”*/”
)
{
}
typedef
alternative<alternative<space_parser, sequence<sequence<
strlit<const char*>, kleene_star<difference<anychar_parser,
chlit<char> > > >, chlit<char> > >, sequence<sequence<
strlit<const char*>, kleene_star<difference<anychar_parser,
strlit<const char*> > > >, strlit<const char*> > >
skip_t;
skip_t skip;
skip_t const& start() const { return skip; }
};
};
请注意中间的那个skip_t的类型定义,我相信一定没有人相信是人手写出来的……本来应该编译器做的事情换做人来做,太疯狂了!特别是还要自己弄明白所有的operator的返回类型,更加的困难……
boost.spirit的作者Joel de Guzman给出了一个比较猥琐的解决方案:将skip_t先定义成随便一个什么类型,比如int,然后编译,这样便一起就会提示说:“Cannot convert boost::spirit::alternative<… …………> to int”,把这个错误拷贝出来,就可以看到实际的类型了——天哪,足够疯狂了!这还没完,如果遇到更复杂的情况怎么办,看看boost 1.34.0的libs/spirit/example/techniques/no_rules/no_rule3.cpp吧,头开始疼了……
所以,Joel de Guzman在给我留下深刻印象之后开始说明他的方案。
方案一:期待C++ 0X早日成为事实的标准,让那个非常有用的auto关键字成为到处可用的东西。
方案二:使用部分编译器已经实现的typeof关键字,然后写一个宏:
程序代码
#define RULE(name, definition) typeof(definition) name = definition
这样,就可以简单的使用宏来简化各种类型定义,不过可怜的MSVC并没有实现这种功能。
方案三:使用“Nabialek trick”。看到“Nabialek trick”这个陌生的名词,我的第一个反应就是“去google/baidu”,不过很可惜,google上的所有搜索结果都直接或间接的来自我正在看的boost.spirit文档,而baidu更是告诉我找不到。OK,我们还是回到这个名词本身吧。这个trick是因为其发明者,Sam Nabialek,而得名,其作用是将“线性非决定式”(linear non-deterministic)转换成“决定式”(deterministic)。如果要说的更具体一些就不行了,其实我还不太明白,我正在阅读boost.spirit的部分源码,希望能够有些收获。无论如何,boost.spirit最终用极为优雅的方式将类型推导的难题再次交给了编译器,整个代码非常的简洁明了,可谓是最佳的实现方式。
就我个人来说,应该还可以提出方案四:使用boost.typeof库的基础设施,不过为了能使用这个东西,依然必须手动的“注册”各种类型,并依赖于大量的模板和宏技巧,比较烦,所以并不算一个很好的方式,但至少还可以接受。
综合来说,正是因为Expression Templates的广泛使用,类型推导成了一个疯狂的怪兽,而C++ 0X中的auto关键字成了绝对的救星,我们还是来呼唤新标准早日降临吧!
使用boost.spirit制作一个简单的四则计算器
从传统意义上来说,boost.spirit库是一个类似于yacc的库,主要业务是做词法解析,然后提供各种读取数据的接口,但是由于这是一个用C++实现、并且大量运Expression Templates技巧的库,所以各种功能可以用非常快捷的方式实现,非常的好用,几乎把C++的各种优良特性都充分发挥出来。
在此,我就从boost.spirit库中间的一个例子出发,经过简单的修改就变成一个极为健壮的四则计算器。我所参考的例子是boost 1.34.0的libs/spirit/example/fundamental/calc_plain.cpp,我因为是在这上面直接修改而来,所以源码中带有原作者的版权声明。
关于boost.spirit的用法,在这里我先不说,以后有时间我来慢慢的把它用中国话讲解一遍。这个程序的核心实际上是一个EBNF的表达式,也就是如何用EBNF语法来表示四则运算。
在这里,我就直接给出答案(EBNF的知识请暂时自行看编译原理的教材):
程序代码
expression ::= term ( (‘+’ term) | (‘-’ term) )*
term ::= factor ( (‘*’ factor) | (‘/’ factor) )*
factor ::= REAL | ’(‘ ex ’)’ | (‘-’ factor) | (‘+’ factor)
其中,expression就是我们需要的表达式pattern。注意这里,正是由于expression首先去匹配term,而term首先去匹配factor,最后factor是以“纯数字”、“括号”、“正负符号”的顺序匹配,term则是以factor、“乘法”、“除法”的顺序匹配,而expression是以term、“加法”、“减法”的顺序匹配,所以就保证了整个表达式匹配过程是按照四则运算的先后顺序进行的。在匹配的过程中只要安插各种“监视”的函数(boost.spirit里面的术语叫做“actor”),就可以轻松实现四则运算。
把EBNF对应到boost.spirit里,具体的grammer类实现如下:
程序代码
struct calculator : public grammar<calculator>
{
template <typename ScannerT>
struct definition
{
definition(calculator const& /*self*/)
{
expression
= term
>> *( (‘+’ >> term[do_calc<do_add>()])
| (‘-’ >> term[do_calc<do_substract>()])
)
;
term
= factor
>> *( (‘*’ >> factor[do_calc<do_multiply>()])
| (‘/’ >> factor[do_calc<do_divide>()])
)
;
factor
= real_p[&push_real]
| ’(‘ >> expression >> ’)’
| (‘-’ >> factor[&do_neg])
| (‘+’ >> factor)
;
}
rule<ScannerT> expression, term, factor;
rule<ScannerT> const&
start() const { return expression; }
};
};
请注意,calculator从grammer派生,但是除了在内部定义了一个嵌套class以外,并没有做更多的事情。
至于这里面用到的do_calc<>、push_real等functor和函数,则是一些极为简单的东西,实现如下:
程序代码
namespace
{
stack<double> calc_stack;
struct do_add
{
double operator () (double lhs, double rhs) const
{
return lhs + rhs;
}
};
struct do_substract
{
double operator () (double lhs, double rhs) const
{
return lhs - rhs;
}
};
struct do_multiply
{
double operator () (double lhs, double rhs) const
{
return lhs * rhs;
}
};
struct do_divide
{
double operator () (double lhs, double rhs) const
{
return lhs / rhs;
}
};
template <typename op>
struct do_calc
{
void operator () (const char *, const char *) const
{
double result = calc_stack.top();
calc_stack.pop();
result = op()(calc_stack.top(), result);
calc_stack.pop();
calc_stack.push(result);
}
};
void push_real(double d)
{
calc_stack.push(d);
}
void do_neg(char const*, char const*)
{
cout << ”NEGATE\n”;
double result = calc_stack.top();
calc_stack.pop();
calc_stack.push(-result);
}
double show_result()
{
return calc_stack.top();
}
}
可以从源码中清晰看到,这些函数就是简单的做了些出栈/入栈以及运算的工作,每一个函数功能极为单纯,可认为就是一些状态及处理函数而已,而状态机逻辑则由grammer搞定了。
令人害怕的C++错误输出
这几天详细阅读了boost不少库的文档,特别详细研究了一下boost.spirit库,一个非常令人惊叹的LL词法分析库。关于这个库本身,我以后有时间再慢慢介绍,真的非常让人惊叹,不过今天我要惊叹的是使用它时得到的错误信息……
我用VS2005编译,错误信息如下:
程序代码
d:\boost\boost_1_34_0\boost\spirit\core\scanner\scanner.hpp(130) : error C2198: ’void (__cdecl *const )(const char *,const char *)’ : too few arguments for call
d:\boost\boost_1_34_0\boost\spirit\core\scanner\scanner.hpp(161) : see reference to function template instantiation ’void boost::spirit::attributed_action_policy<AttrT>::call<void(__cdecl *)(const char *,const char *),IteratorT>(const ActorT &,const double &,const IteratorT &,const IteratorT &)’ being compiled
with
[
AttrT=const double,
IteratorT=iterator_t,
ActorT=void (__cdecl *)(const char *,const char *)
]
d:\boost\boost_1_34_0\boost\spirit\core\composite\actions.hpp(109) : see reference to function template instantiation ’void boost::spirit::action_policy::do_action<void(__cdecl *)(const char *,const char *),const T,iterator_t>(const ActorT &,AttrT &,const IteratorT &,const IteratorT &) const’ being compiled
with
[
T=double,
ActorT=void (__cdecl *)(const char *,const char *),
AttrT=double,
IteratorT=iterator_t
]
d:\boost\boost_1_34_0\boost\spirit\core\composite\alternative.hpp(60) : see reference to function template instantiation ’boost::spirit::match<T> boost::spirit::action<ParserT,ActionT>::parse<ScannerT>(const ScannerT &) const’ being compiled
with
[
T=double,
ParserT=boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,
ActionT=void (__cdecl *)(const char *,const char *),
ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
]
d:\boost\boost_1_34_0\boost\spirit\core\composite\alternative.hpp(60) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::alternative<A,B>::parse<ScannerT>(const ScannerT &) const’ being compiled
with
[
A=boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,
B=boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>,
ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
]
d:\boost\boost_1_34_0\boost\spirit\core\composite\alternative.hpp(60) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::alternative<A,B>::parse<ScannerT>(const ScannerT &) const’ being compiled
with
[
A=boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,
B=boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>,
ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\rule.ipp(233) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::alternative<A,B>::parse<ScannerT>(const ScannerT &) const’ being compiled
with
[
A=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,
B=boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,
ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\rule.ipp(232) : while compiling class template member function ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::impl::concrete_parser<ParserT,ScannerT,AttrT>::do_parse_virtual(const ScannerT &) const’
with
[
ParserT=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>>,
ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>,
AttrT=boost::spirit::nil_t
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\rule.hpp(135) : see reference to class template instantiation ’boost::spirit::impl::concrete_parser<ParserT,ScannerT,AttrT>’ being compiled
with
[
ParserT=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>>,
ScannerT=boost::spirit::scanner<const char *,boost::spirit::scanner_policies_t>,
AttrT=boost::spirit::nil_t
]
e:\realdodo\my project\testboostwavequickstart\cacl_plain.cpp(130) : see reference to function template instantiation ’boost::spirit::rule<T0> &boost::spirit::rule<T0>::operator =<boost::spirit::alternative<A,B>>(const ParserT &)’ being compiled
with
[
T0=scanner_t,
A=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,
B=boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,
ParserT=boost::spirit::alternative<boost::spirit::alternative<boost::spirit::alternative<boost::spirit::action<boost::spirit::real_parser<double,boost::spirit::real_parser_policies<double>>,void (__cdecl *)(const char *,const char *)>,boost::spirit::sequence<boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>,boost::spirit::chlit<char>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::action<boost::spirit::rule<scanner_t>,void (__cdecl *)(const char *,const char *)>>>,boost::spirit::sequence<boost::spirit::chlit<char>,boost::spirit::rule<scanner_t>>>
]
e:\realdodo\my project\testboostwavequickstart\cacl_plain.cpp(109) : while compiling class template member function ’calculator::definition<ScannerT>::definition(const calculator &)’
with
[
ScannerT=scanner_t
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\grammar.ipp(262) : see reference to class template instantiation ’calculator::definition<ScannerT>’ being compiled
with
[
ScannerT=scanner_t
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\impl\grammar.ipp(281) : see reference to function template instantiation ’void boost::spirit::impl::call_helper<0>::do_<result_t,definition_t,ScannerT>(RT &,DefinitionT &,const ScannerT &)’ being compiled
with
[
ScannerT=scanner_t,
RT=result_t,
DefinitionT=definition_t
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\grammar.hpp(53) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::impl::grammar_parser_parse<0,calculator,boost::spirit::parser_context<>,ScannerT>(const boost::spirit::grammar<DerivedT> *,const ScannerT &)’ being compiled
with
[
ScannerT=scanner_t,
DerivedT=calculator
]
d:\boost\boost_1_34_0\boost\spirit\core\non_terminal\grammar.hpp(63) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::grammar<DerivedT>::parse_main<ScannerT>(const ScannerT &) const’ being compiled
with
[
DerivedT=calculator,
ScannerT=scanner_t
]
d:\boost\boost_1_34_0\boost\spirit\core\scanner\impl\skipper.ipp(131) : see reference to function template instantiation ’boost::spirit::match<boost::spirit::nil_t> boost::spirit::grammar<DerivedT>::parse<scanner_t>(const ScannerT &) const’ being compiled
with
[
DerivedT=calculator,
ScannerT=scanner_t
]
d:\boost\boost_1_34_0\boost\spirit\core\scanner\impl\skipper.ipp(153) : see reference to function template instantiation ’boost::spirit::parse_info<> boost::spirit::impl::phrase_parser<boost::spirit::space_parser>::parse<IteratorT,DerivedT>(const IteratorT &,const IteratorT &,const ParserT &,const boost::spirit::space_parser &)’ being compiled
with
[
IteratorT=const char *,
DerivedT=calculator,
ParserT=calculator
]
d:\boost\boost_1_34_0\boost\spirit\core\scanner\impl\skipper.ipp(171) : see reference to function template instantiation ’boost::spirit::parse_info<> boost::spirit::parse<const CharT*,DerivedT,boost::spirit::space_parser>(const IteratorT &,const IteratorT &,const boost::spirit::parser<DerivedT> &,const boost::spirit::parser<boost::spirit::space_parser> &)’ being compiled
with
[
CharT=char,
DerivedT=calculator,
IteratorT=const char *
]
e:\realdodo\my project\testboostwavequickstart\cacl_plain.cpp(160) : see reference to function template instantiation ’boost::spirit::parse_info<> boost::spirit::parse<_Elem,DerivedT,boost::spirit::space_parser>(const CharT *,const boost::spirit::parser<DerivedT> &,const boost::spirit::parser<boost::spirit::space_parser> &)’ being compiled
with
[
_Elem=char,
DerivedT=calculator,
CharT=char
]
我数了一下,这个错误信息总共有11355个字符,并且完全没有告诉我究竟应该如何排除这个错误……不过幸好在错误信息的最后几行中指出了真正出错的地方(真的是这样,前面的输出完全是没有用的烟雾弹……),然后利用这一点点信息,结合boost.spirit的文档,我终于知道错误的原因:我把一个函数的输入参数类型和个数写错了。
OK,看来调试这种基于Expression Templates的库真的需要耐心和勇气。
一个通用的常量迭代器(const_iterator)包装类
最近在写代码的时候遇到这样一个功能需求:在一个类里面使用了一个容器,现在希望将里面的数据用只读方式暴露给外部来读取。这个功能当然很好实现,只需要在这个类里面实现公有的begin()/end()方法,实现时直接调用容器的begin()/end()函数即可。可是过了不一会,新的问题冒出来了:因为我突然发现容器里面的数据是原始数据,还需要对每个数据作一次变换才能使用,而我一方面需要保留这些原始数据,又不想另开缓冲区来存储变换后的数据,那么就只能在访问的同时变换数据。那么,需要解决的技术难题就是:如何能在使用迭代器访问的同时透明的对数据内容进行一次变换?
请看下面的代码,这里我将问题简化为以下的类:
程序代码
class DataSource
{
typedef ::std::vector<FLOAT> StorageType;
typedef StorageType::const_iterator const_iterator;
StorageType data_;
public:
const_iterator begin() const
{
return data_.begin();
}
const_iterator end() const
{
return data_.end();
}
// 其他省略
};
// 以下为调用代码,source是DataSource的一个实例
DataSource::const_iterator first = source.begin();
foo(*first); // 此时我想临时变换source.data_中的数据怎么办?
在容器本身上动手术并不明智,因为我的最终目的是要让这个DataSource能够“侦听”到数据访问的“消息”并在其中做手脚,把容器和具体类的实现邦定在一起终究会出问题的,所以我把目光放在了const_iterator这个迭代器本身上。
于是,我设计了一个包装类来解决这个问题,基本思想是:包装类实现迭代器的所有concept,并使用迭代器和Functor一起作为参数来构造这个包装类,使得operator *和operator ->的重载中可以使用Functor来临时修改容器中读出来的数据。
完整的代码直接给出来好了,这是一个最简单但可用的类:
程序代码
template <typename ContainerType, typename Functor>
class const_iterator_wrapper
{
typedef const_iterator_wrapper MyType;
typedef typename ContainerType::const_iterator IteratorType;
typedef typename ContainerType::value_type ValueType;
ValueType value_;
IteratorType it_;
Functor func_;
public:
const_iterator_wrapper(const IteratorType & it, const Functor & func = Functor())
: it_(it)
, func_(func)
, value_()
{
}
const ValueType & operator * ()
{
value_ = *it_;
func_(value_);
return value_;
}
const ValueType * operator -> ()
{
value_ = *it_;
func_(value_);
return &value_;
}
MyType & operator ++ ()
{
++it_;
return *this;
}
MyType operator ++ [...]
