📄 item_063.htm
字号:
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>你的库散布得越广,你对库的客户使用的构建(</span><span
lang=EN-US>build</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>)环境的控制就越小,你可以在库的外部接口中可靠地使用的类型就越少。模块间的交互涉及到二进制数据的交换。唉,</span><span
lang=EN-US>C++</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>没有指定一个标准的二进制交互方法。为了和外界进行交互,广为散布的库可能要特别依赖于内建类型,如</span><span
class=SpellE><span lang=EN-US>int</span></span><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>和</span><span
lang=EN-US>char</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>。即便是编译相同的类型,使用相同的编译器,不同的编译选项,得到的二进制表示仍有可能不兼容。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>一般来说,要么你能够控制用来构建模块的编译器和编译选项,这样你就可以使用任何类型——要么你无法控制这些,这样你就只能使用平台提供的类型和</span><span
lang=EN-US>C++</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>的内建类型(即便如此,在后一种情况下你应该在文档中说明预期的类型大小和表示方法)。尤其是,不要在模块的接口中使用标准库的类型,除非使用它的所有其它模块会和它同时编译,并使用完全相同的标准库实现。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>对由于使用非所有客户都能正确理解的类型而引起的问题,以及由于使用低层抽象而引起的问题,这里有一个权衡。抽象是重要的,如果某些客户只能理解低层的类型,那么你必须使用这些类型,但可以考虑另外提供可选的操作,这些操作使用高层类型。考虑函数</span><span
class=SpellE><span lang=EN-US>SummarizeFile</span></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>,它的参数是将要被处理的文件。对该参数,有三种常见的选择:可以是一个</span><span lang=EN-US>char*</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>指针,指向以</span><span lang=EN-US>C</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>风格字符串表示的文件名;可以是一个</span><span lang=EN-US>string</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类型的文件名;也可以是一个</span><span class=SpellE><span lang=EN-US>istream</span></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>或自定义的</span><span lang=EN-US>File</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>对象。每个选择都是一个权衡:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l0 level1 lfo1;tab-stops:list 36.0pt'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>选择</span><span lang=EN-US>1</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>:</span><span lang=EN-US>char*</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>。显然能够访问</span><span lang=EN-US>char*</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类型的客户群最广。不幸的是,这也是最底层的选择。特别是,它不够健壮(例如:调用方和被调用方必须明确由谁分配和释放内存),比较容易遇到错误(例如:文件可能不存在),也不够安全(例如:对典型的缓冲区溢出攻击来说)。</span></li>
</ul>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l0 level1 lfo1;tab-stops:list 36.0pt'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>选择</span><span lang=EN-US>2</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>:</span><span lang=EN-US>string</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>。能够访问</span><span lang=EN-US>string</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类型的客户有更多的限制,它们必须用</span><span lang=EN-US>C++</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>编写,必须使用相同的标准库实现,相同的编译器,以及兼容的编译器设置。更多的限制换来的是更健壮(例如:调用方和被调用方可以不必关心内存管理的问题,但请参见第</span><span
lang=EN-US>60</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>条),更安全(例如:</span><span
lang=EN-US>string</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>会在需要时扩大内部的缓冲区,不易遭受缓冲区溢出攻击)。但这种选择仍然相对低层,因此还是会遇到错误,这些错误必须显式地检测(例如:文件可能不存在)。</span></li>
</ul>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l0 level1 lfo1;tab-stops:list 36.0pt'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>选择</span><span lang=EN-US>3</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>:</span><span class=SpellE><span lang=EN-US>istream</span></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>或</span><span lang=EN-US>File</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>。如果你无论如何要使用类类型(</span><span lang=EN-US>class type</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>),这样就要求客户程序用</span><span lang=EN-US>C++</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>编写,并用相同的编译器和编译开关编译,那么请使用强抽象:</span><span class=SpellE><span
lang=EN-US>istream</span></span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>(或自定义的</span><span
lang=EN-US>File</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>对象,它封装了</span><span
class=SpellE><span lang=EN-US>istream</span></span><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>,这样就可以避免直接依赖于某个标准库实现)提高了抽象的层次,并使得</span><span
lang=EN-US>API</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>健壮得多。函数知道自己得到的是一个</span><span
lang=EN-US>File</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>或是相应的输入流,它不需要为文件名管理内存,也不受其它两种选择可能会遇到的许多有意无意的错误的影响。现在只剩很少的东西需要检查:文件必须已经被打开,文件内容的格式必须正确,但这些错误是无论如何都可能遇到的。</span></li>
</ul>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>即使选择在模块的外部接口中使用较低层的抽象,也还是应该始终在内部使用最高层的抽象,并在模块的边界处将之转换为低层抽象。例如,如果你的一些客户不使用</span><span
lang=EN-US>C++</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>,那么可以给客户代码使用不透明的</span><span
lang=EN-US>void*</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>或</span><span class=SpellE><span
lang=EN-US>int</span></span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>句柄,但在内部仍然使用对象,并且只在模块的接口中进行低层抽象和高层抽象的相互转换。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><b style='mso-bidi-font-weight:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>示例</span></b></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><i style='mso-bidi-font-style:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>示例:在模块的接口中使用</span><span class=SpellE><span lang=EN-US>std::string</span></span></i><i
style='mso-bidi-font-style:normal'><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>。</span></i><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>假设一个模块希望提供下面这个</span><span lang=EN-US>API</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span class=SpellE><span
class=GramE><span lang=EN-US>std::string</span></span></span><span lang=EN-US>
Translate( const <span class=SpellE>std::string</span>& );</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>对于一个团队或公司内部使用的库,通常这样就行了。但是如果需要把该模块与使用了不同的</span><span
class=SpellE><span lang=EN-US>std::string</span></span><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>实现(具有不同的内存布局)的调用方代码动态链接到一起,就会发生一些奇怪的事情,因为客户和模块彼此不能理解对方的</span><span
lang=EN-US>string</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>我们见过一些开发人员,他们把</span><span
class=SpellE><span lang=EN-US>std::string</span></span><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>封装在自定义的</span><span
class=SpellE><span lang=EN-US>CustomString</span></span><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>中,试图以此来绕过这个问题,但是令他们吃惊的是他们还是会遇到一模一样的问题,这是因为他们无法控制调用方代码的构建过程。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>一个解决方案是用可移植的(可能是内建的)类型,来代替函数中的</span><span
lang=EN-US>string</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>参数类型,或者也可以另外添加一个这样的函数。例如:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span class=GramE><span
lang=EN-US>void</span></span><span lang=EN-US> Translate( const char* <span
class=SpellE>src</span>, char* <span class=SpellE>dest</span>, <span
class=SpellE>size_t</span> <span class=SpellE>destSize</span> );</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>使用低层抽象的可移植性更好,但是增加了复杂性。比如在这个例子中,如果缓冲区不够长,那么调用方和被调用方必须明确处理可能发生的字符串截断。(注意这个版本使用的缓冲区由调用方分配,以避免在不同的模块中分配和释放内存所带来的隐患;参见第</span><span
lang=EN-US>60</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条。)</span></p>
</div>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -