** 依然热恋 C++ **
Stanley B. Lippman 著 荣耀 译
** 摘要: ** 使用 C++已多年的程序员现在正处于迷惑之中:他们的语言如何面对C#和微软.NET的来临?本文勾画了C++如何应用于.NET世界的路标。在.NET中,可以两种方式使用C++代码:托管(managed)和非托管(unmanaged)。非托管代码不使用CLR,而托管代码则致力于使用C++托管扩展。本文讨论了这两种方式。
我们这些 C++社团里的人第一次感觉自己象是一个刚刚降临了一个新宝贝的家庭中年长的孩子,每一个人都狂热地围着这个新来的小家伙,此刻,即便能有人轻轻地拍一拍我们的脑袋,我们就很幸运了。我们很难不产生被忽视和一丁点儿伤害的感觉。在技术上这实际更糟糕,因为基础不断发生漂移,与之并进,事关生存。
当然,如今微软 .NET框架是正在出售的新技术,多么丰盛的大餐啊!每一个人都对那个叫C#的语言啧啧不已。如果你是一名C++程序员,你很难不怀疑你是否应该学习C#,毕竟,在.NET讨论中,要不是拿它跟C#对比,难得提到一次C++。
C++程序员过时了吗?绝对不是!本文中,我将简介Visual Studio .NET当前版本中的新东西,并给你一些微软关于C++将来计划的想法。首先,我将简介Visual Studio.NET中C++的两个方面:标准C++和C++托管扩展。
保持兼容是标准 C++最新进展背后的主要动机,也就是说,提供对ISO语言特性的支持。我还会讨论C++托管扩展,它使C++成为一种.NET语言。
** Visual Studio .NET中的标准C++ **
标准兼容方面,已经在如下领域取得进展:
l 1. 虚函数定义现在支持协变返回类型(covariant return type),对于编写类层次结构的人来说,这真是个喜讯。
l 2. 静态整型常量成员现在可被显式初始化。
l 3. 加入了对 main中隐式返回0的支持。
下面,我将简要讨论每一个领域。
加入协变返回类型是对 C++语言的第一个修改,已经标准委员会批准。这是奇妙的—派生类的虚函数现在可以返回一个派生类的实体,而它所重载的基类中的虚函数返回一个基类的实体。这是对类层次结构的一个重要的设计习语支持,它也是Visual Studio .NET中一个受欢迎的“附加物”。
这儿有一个典型用法的例子。考察抽象基类 Query:
class Query
{
public:
virtual Query *clone() = 0;
//...
};
在派生类 NotQuery的实体中,clone返回一个NotQuery对象的拷贝。例如:
class NotQuery //【译注:此类的声明有点问题】
{
public:
virtual ???? clone() {return new NotQuery(this);}
//...
public:
Query *operand;
};
假如没有对协变返回类型的支持, NotQuery实体clone的返回类型必须为Query*:
//没有协变返回类型支持...
virtual Query* clone() {return new NotQuery(this);}
这会工作得很好,如果你将 clone的返回值赋给一个Query*指针,就象这样:
NotQuery::NotQuery(const NotQuery &rhs) {operand = rhs.operand->clone();}
但这样不成—当你希望显式将其赋给一个 NotQuery*时,如下:
NotQuery nq(new NameQuery("Shakespeare"); //【译注:少了个“)”】。
//oops: 非法赋值...
NotQuery *pnq0 = nq->clone();
//ok: 需显式造型转换...
NotQuery *pnq1 = static_cast
1<notquery*>(nq->clone());
2
3有了对协变返回类型的支持,你可以显式声明 NotQuery的clone返回一个NotQuery*:
4
5//有了协变返回类型支持...
6
7virtual NotQuery* clone() {return new NotQuery(this);}
8
9这就允许你以一种直觉的方式来使用 clone,而无需显式造型转换:
10
11//ok: 隐式转换...
12
13Query *pq = nq->clone();
14
15//ok: 无需转换...
16
17NotQuery *pnq = nq->clone();
18
19尽管静态整型常量成员的显式初始化并不象协变返回类型这样的语言特性来得那么紧要,但在类需要常量表达式的情况下,它提供了重大的设计便利,比如用于固定尺寸的数组定义中。例如:
20
21class Buffer
22
23{
24
25static const int ms_buf_size = 1024;
26
27char m_buffer[ms_buf_size];
28
29};
30
31静态整型常量成员是 C++中唯一可在类定义中被显式初始化的类成员,从而使得该成员的值可被类中的其它成分使用,比如m_buffer的维数尺寸。否则,一般使用枚举作为符号常量代替。
32
33main的返回值表示程序的退出状态。约定俗成,0表示程序成功退出。对于标准C++,没有显式返回值的main意味着编译器将在其尾部之前插入一行
34
35return 0 ;
36
37Visual Studio .NET中的Visual C++终于遵从了这个标准。
38
39** Visual Studio .NET中的 Managed C++ **
40
41Visual Studio .NET中C++托管扩展主要支持以下三个应用策略:
42
43l 1\. 为非托管 API提供托管.NET包装类,从而可将现存的C++类暴露给微软.NET平台。
44
45l 2.允许你使用微软 .NET类框架,并可与unmanaged C++混用。对框架来说有三个方面:核心语言支持,例如集合类和系统I/O;基础编程类,如对线程、网络套接字和正则表达式的支持;应用领域支持,例如XML、ASP.NET、Web Services、Windows Forms和ADO.NET等等。
46
47l 3.你可以直接在 .NET环境中编写,就好比在C#和Visual Basic中一样。不过,这个版本中的C++还未提供对RAD设计者的支持,比如Windows Forms和Web Forms。
48
49首先,我将讨论包装一个非托管的实现。在我的《 C++ Primer》第三版(Addison-Wesley, 1998)一书里,我创建了一个比较大的文本查询应用,它着重练习STL容器类,用以解析文本文件并建立内部表示。表1展示了包含文件,表2展示了数据表示。
50
51** 表 1 文本查询应用的包含文件 **
52
53#include <algorithm>
54
55#include <string>
56
57#include <vector>
58
59#include <utility>
60
61#include <map>
62
63#include <set>
64
65#include <iostream>
66
67#include <fstream>
68
69#include <stddef.h>
70
71#include <ctype.h>
72
73using namespace std;
74
75---
76
77** 表 2 数据表示 **
78
79typedef pair<short,short> location;
80
81typedef vector<location> loc;
82
83typedef vector<string> text;
84
85typedef pair<text*,loc*> text_loc;
86
87class TextQuery
88
89{
90
91public:
92
93// ...
94
95private:
96
97vector<string> *lines_of_text;
98
99text_loc *text_locations;
100
101map<string,loc*> *word_map;
102
103Query *query;
104
105static string filt_elems;
106
107vector<int> line_cnt;
108
109};
110
111---
112
113Query是一个解释查询语言的面向对象层次结构的抽象基类。表3展示了一个查询会话的可能运行方式。文本查询系统的一个本地调用看起来象下面这个样子:
114
115int main()
116
117{
118
119TextQuery tq;
120
121tq.build_up_text();
122
123tq.query_text();
124
125}
126
127** 表 3 查询会话 **
128
129Enter a query-please separate each item by a space.
130
131Terminate query (or session) with a dot( . ).
132
133==> fiery && ( bird || shyly )
134
135fiery ( 1 ) lines match
136
137bird ( 1 ) lines match
138
139shyly ( 1 ) lines match
140
141( bird || shyly ) ( 2 ) lines match
142
143fiery && ( bird || shyly ) ( 1 ) lines match
144
145Requested query: fiery && ( bird || shyly )
146
147( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
148
149---
150
151我希望无需做什么修修补补的事情(更不要说重新实现它了)就可将 TextQuery接口暴露给.NET平台。毕竟,它能够工作。我心里没底—是不是如果我想移到.NET平台就意味着我必须放弃在本地代码上花费的功夫?幸运地是,将本地代码进行包装以暴露给.NET平台,是managed C++的拿手好戏。表4展示了干这种事的代码的可能模样。
152
153** 表 4 包装本地C++类 **
154
155#include "TextQuery.h"
156
157__gc class TextQueryNet
158
159{
160
161private:
162
163TextQuery *pquery;
164
165public:
166
167TextQueryNet() : pquery(new TextQuery()){}
168
169~TextQueryNet() {delete pquery;}
170
171void query_text() {pquery->query_text();}
172
173void build_up_text() {pquery->build_up_text();}
174
175// ...
176
177};
178
179---
180
181__gc修饰符(见表4所示)将该类标记为一个托管类—一个配置于CLR托管堆上的被垃圾收集的类。我将本地类声明为指针成员,利用表达式new将其配置在非托管堆上,就象我在本地代码里做的一样。因为它不被垃圾收集,我在析构器里将它delete掉。build_up_text和query_text都充当存根函数(stub functions),将调用派发到实际的TextQuery对象。
182
183表 5展示了修改过的main函数,它是由managed C++项目向导生成的。
184
185** 表 5 生成的main函数 **
186
187#include "stdafx.h"
188
189#using <mscorlib.dll>
190
191#include <tchar.h>
192
193using namespace System;
194
195#include "gc_TextQuery.h"
196
197int _tmain(void)
198
199{
200
201Console::WriteLine(S"Beginning managed wrapper test ...");
202
203TextQueryNet *tqn = new TextQueryNet();
204
205tqn->build_up_text();
206
207tqn->query_text();
208
209Console::WriteLine(S"Ending managed wrapper test ...");
210
211return 0;
212
213}
214
215---
216
217现在让我们来讨论如何使用 .NET框架。假如有人问我ISO标准C++最严重的缺点是什么,我会说是这样的一个事实:没有诸如线程、网络编程、正则表达式和XML等编程领域的标准库。标准委员会计划在下一轮时间里弥补这个不足,但离现在还有几年,这期间你怎么办?幸运地是,.NET框架提供了一个颇具吸引力的解决方案。例如,表6展示了一个使用了.NET框架的简单套接字服务器类。它接受对Northwind的employees的电话号码查询,Northwind是一个同Visual Studio .NET一起分发的样例SQL数据库。
218
219** 表 6 简单套接字服务器类 **
220
221//包含必要的装配件(assemblies)
222
223#using <mscorlib.dll>
224
225#using <system.dll>
226
227#using <system.data.dll>
228
229#using <system.xml.dll>
230
231//打开相关名字空间
232
233using namespace System;
234
235using namespace System::Threading;
236
237using namespace System::Data;
238
239using namespace System::Data::SqlClient;
240
241using namespace System::Collections;
242
243using namespace System::Net::Sockets;
244
245//ok: 这儿是我们的类
246
247__gc class SocketDemo_Server
248
249{
250
251private:
252
253static const int port = 4554;
254
255static const int maxPacket = 128;
256
257TcpListener *tcpl;
258
259DataSet *ds;
260
261DataRowCollection *rows;
262
263public:
264
265SocketDemo_Server();
266
267void Start();
268
269void handleConnection();
270
271//grab the data from the SQL database
272
273void retrieveData();
274
275};
276
277---
278
279表 7是运行这个服务器并记录其处理三个客户请求的示例输出。表8则展示了表6中所示的Start函数的实现。Start函数使用Thread类生成独立线程,以从数据库中取得数据并处理客户连接。因为WriteLine需要一个引用型的参数,因此必须将port值显式装箱(box)。
280
281** 表 7 服务器输出 **
282
283Server[4554]: OK: started TcpListener ...
284
285Server[4554]: OK: listening for connections ...
286
287Server[4554]: OK: retrieved SQL database info ...
288
289Server[4554]: OK: a client connected ...
290
291Server[4554]: OK: client requested phone # for Fuller
292
293Server[4554]: OK: first request for Fuller
294
295Server[4554]: Phone number for Fuller: (206) 555-9482
296
297Server[4554]: OK: a client connected ...
298
299Server[4554]: OK: client requested phone # for King
300
301Server[4554]: OK: first request for King
302
303Server[4554]: Phone number for King: (71) 555-5598
304
305Server[4554]: OK: a client connected ...
306
307Server[4554]: OK: client requested phone # for Fuller
308
309Server[4554]: OK: cached request for Fuller
310
311Server[4554]: Phone number for Fuller: (206) 555-9482
312
313Server[4554]: OK: a client connected ...
314
315Server[4554]: OK: client requested phone # for Musil
316
317Server[4554]: OK: first request for Musil
318
319Server[4554]: Phone number for Musil: Sorry. Cannot be found.
320
321---
322
323<TABLE style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium non</system.xml.dll></system.data.dll></system.dll></mscorlib.dll></tchar.h></mscorlib.dll></int></string,loc*></string></text*,loc*></string></location></short,short></ctype.h></stddef.h></fstream></iostream></set></map></utility></vector></string></algorithm></notquery*>