条款3:大小写不敏感的字符串--之二(1)
我们在条款2中创建了ci_string类,那么这个类的可用性到底如何?现在就来关注这个类的可用性,看看将会遇到怎样的设计问题或者需要做哪些的权衡。此外,我们还将解决其中一些遗留问题。
我们再来看看条款2中的解答(暂时忽略函数体部分):
- struct ci_char_traits : public char_traits<char>
- {
- static bool eq( char c1, char c2 ) { /*...*/ }
- static bool lt( char c1, char c2 ) { /*...*/ }
- static int compare( const char* s1,
- const char* s2,
- size_t n ) { /*...*/ }
- static const char*
- find( const char* s, int n, char a ) { /*...*/ }
- };
在本条款中,我们要尽可能完整地回答下列相关问题:
1.将ci_char_traits从char_traits<char>继承下来,这种做法是否安全?
2.为什么在编译下面代码时会失败?
- ci_string s = "abc";
- cout << s << endl;
3.如果使用其他的运算符(例如, +, +=, =),并将string对象和ci_string对象作为参数,结果将会怎样?例如:- string a = "aaa";
- ci_string b = "bbb";
- string c = a + b;
解答
上述三个问题的解答如下:
1.将ci_char_traits从char_traits<char>继承下来,这种做法是否安全?
公有继承通常用来对"IS-A"或者"WORKS-LIKE-A"这样的关系进行建模,在Liskov替换规则(LSP)中描述了这种情况(请参见条款22和条款28)。不过,本例是LSP中一个非常特殊的情况:因为我们的主要意图并不是要通过基类char_traits<char>的指针或引用以多态的方式来使用ci_char_traits对象。标准库也不会通过多态的方式来使用trait对象。在这种情况下,使用继承只是为了实现上的便利性(当然,有些人可能会说这是在偷懒),因此在这里并不是遵循面向对象的方式来使用继承。
此外,在其他方面也用到了LSP:在编译期用到了LSP,编译期的派生类对象与基类对象必须是WORK-LIKE-A关系,这种行为是basic_string模板的需求定义所要求的。在新闻组上,Nathan Myers 是这样来描述的:
换句话说,LSP在这里仅应用于编译期,并且按照惯例我们将其称为"需求列表"。我希望将这个示例与其他示例区分开来,将这个示例中应用的规则定义为范型Liskov替换规则(Generic Liskov Substitution Principle,GLSP):任何作为模板参数的类型(或者模板),都应该与模板参数的需求列表保持一致。
从迭代器的tag或者trait中派生出来的类,都应该符合GLSP,而在传统LSP中的一些要素(例如,虚析构函数等),可以实现也可以不实现,这取决于在需求列表中是否定义了运行期的多态行为。
因此,简单来说,这里的继承是安全的,因为它符合GLSP(而不是LSP)。不过,我在这里使用继承并不仅仅只是为了便利性(避免编写char_traits<char>的其他功能代码),而且还为了说明其中的区别--只需修改4个函数就能获得想要的效果。
提出第一个问题的目的是要让你思考以下几点:(1)对继承的合理使用(以及不合理的使用);(2)在ci_char_traits中只包含静态成员,这种做法的意义;(3)永远都不以多态的方式来使用char_traits对象。
2.为什么在编译下面代码时会失败?
- ci_string s = "abc";
- cout << s << endl;
提示:在C++(www.cppentry.com)标准的21.3.7.9 [lib.string.io]中,对basic_string中 operator <<的声明如下所示。 - template<class charT, class traits, class Allocator>
- basic_ostream<charT, traits>&
- operator<<(basic_ostream<charT, traits>& os,
- const basic_string<charT,traits,Allocator>& str);
解答:首先注意到cout实际上是一个basic_ostream<char, char_traits<
char> >类型的对象。接下来,我们就可以指出问题所在了:basic_string 中的operator <<是一个需要模板化的函数,并且在执行流插入操作时,参数basic_ostream中的"char type"和 "traits type" 必须与字符串类中的模板参数一样。也就是说,虽然在上面的解答中ci_char_traits是继承于char_traits<char>,但是正确的operator<<应该是将ci_string输出到一个basic_ostream<char, ci_char_traits>类型的流对象中去,而cout并不是这种类型的流对象。