| Still in Love with C++ 中文版(1) | |
|---|---|
| |

|
|

---|---|---
| | |
[ 发表日期:2002-4-18 15:44:17 ]
| Still in Love with C++ 中文版
Modern Language Features Enhance the Visual C++ .NET Compiler
作者: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 虚函数定义现在支持协变返回类型,对编写类层次结构的人来说,这真是个喜讯。
l 静态整型常量成员现在可被显式初始化。
l 加入了对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意味着编译器将在其尾部之前插入一行return 0。Visual Studio .NET中的Visual C++终于遵从了这个标准。
34
35Visual Studio .NET中受管制的C++
36
37Visual Studio .NET中C++受管制的扩展主要考虑以下三个应用策略:
38
39l 对于不受管制的API,提供了受管制的.NET包装类,从而可将现存的C++类暴露给微软.NET平台。
40
41l 可以使用微软.NET类框架,并可与不受管制的C++混用。对框架来说有三个方面:核心语言支持,例如集合类和系统I/O;基础编程类,如对线程、网络套接字和正则表达式的支持;应用领域支持,例如XML、ASP.NET、Web Services、Windows Forms和ADO.NET等等。
42
43l 你可以直接在.NET环境中编写,就好比在C#和Visual Basic中一样。不过,这个版本中的C++还未提供对RAD设计者的支持,例如Windows Forms和Web Forms。
44
45首先,我将讨论包装一个不受管制的实现。在我的《C++ Primer》第三版(Addison-Wesley, 1998)一书里,我创建了一个比较大的文本查询应用,它着重练习STL容器类,用以解析文本文件并建立内部表示。表1展示了包含文件,表2展示了数据表示。
46
47表1 文本查询应用的包含文件
48
49#include <algorithm>
50
51#include <string>
52
53#include <vector>
54
55#include <utility>
56
57#include <map>
58
59#include <set>
60
61#include <iostream>
62
63#include <fstream>
64
65#include <stddef.h>
66
67#include <ctype.h>
68
69using namespace std;
70
71
72
73
74表2 数据表示
75
76typedef pair<short,short> location;
77
78typedef vector<location> loc;
79
80typedef vector<string> text;
81
82typedef pair<text*,loc*> text_loc;
83
84class TextQuery
85
86{
87
88public:
89
90// ...
91
92private:
93
94vector<string> *lines_of_text;
95
96text_loc *text_locations;
97
98map<string,loc*> *word_map;
99
100Query *query;
101
102static string filt_elems;
103
104vector<int> line_cnt;
105
106};
107
108
109Query是一个解释查询语言的面向对象层次结构的抽象基类。表3展示了一个查询会话的可能运行方式。文本查询系统的一个本地调用看起来和下面相似:
110
111int main()
112
113{
114
115TextQuery tq;
116
117tq.build_up_text();
118
119tq.query_text();
120
121}
122
123表3 查询会话
124
125Enter a query-please separate each item by a space.
126
127---
128|  |
129
130
131
132| 
133---|---|---
134
135
136
137
138| Still in Love with C++ 中文版(2) |
139---|---
140| |
141
142| |
143
144
145
146|  |
147
148
149
150---|---|---
151| | |
152
153[ 发表日期:2002-4-18 15:47:42 ]
154
155---
156| Terminate query (or session) with a dot( . ).
157
158==> fiery && ( bird || shyly )
159
160fiery ( 1 ) lines match
161
162bird ( 1 ) lines match
163
164shyly ( 1 ) lines match
165
166( bird || shyly ) ( 2 ) lines match
167
168fiery && ( bird || shyly ) ( 1 ) lines match
169
170Requested query: fiery && ( bird || shyly )
171
172( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
173
174
175我希望无需做什么修修补补的事情(更不要说重新实现它了)就可将TextQuery接口暴露给.NET平台。毕竟,它能够工作。我心里没底—是不是如果我想移到.NET平台就意味着我必须放弃在本地代码上的投资。幸运地是,包装本地代码以暴露给.NET平台是受管制的C++的拿手好戏。表4展示了干这种事的代码的可能模样。
176
177表4 包装本地C++类
178
179#include "TextQuery.h"
180
181__gc class TextQueryNet
182
183{
184
185private:
186
187TextQuery *pquery;
188
189public:
190
191TextQueryNet() : pquery(new TextQuery()){}
192
193~TextQueryNet() {delete pquery;}
194
195void query_text() {pquery->query_text();}
196
197void build_up_text() {pquery->build_up_text();}
198
199// ...
200
201};
202
203
204__gc修饰符(见表4所示)将该类标记为一个受管制的类—一个配置在CLR受管制堆上的被垃圾收集的类。我将本地类声明为指针成员,利用表达式new将其配置在不受管制的堆上,就象我在本地代码里做的一样。因为它不被垃圾收集,我在析构器里将它delete掉。build_up_text和query_text都充当存根函数,将调用调度到实际的TextQuery对象。
205
206表5展示了修改过的main函数,它是由受管制的C++项目向导生成的。
207
208表5 生成的main函数
209
210#include "stdafx.h"
211
212#using <mscorlib.dll>
213
214#include <tchar.h>
215
216using namespace System;
217
218#include "gc_TextQuery.h"
219
220int _tmain(void)
221
222{
223
224Console::WriteLine(S"Beginning managed wrapper test ...");
225
226TextQueryNet *tqn = new TextQueryNet();
227
228tqn->build_up_text();
229
230tqn->query_text();
231
232Console::WriteLine(S"Ending managed wrapper test ...");
233
234return 0;
235
236}
237
238
239现在让我们来讨论如何使用.NET框架。假如有人问我ISO标准C++最严重的缺点是什么,我会说是这样的一个事实—没有诸如线程、网络编程、正则表达式和XML等编程领域的标准库。标准委员会计划在下一轮时间里弥补这个不足,但离现在还有几年,这期间你怎么办?幸运地是,.NET框架提供了一个颇具吸引力的解决方案。例如,表6展示了一个使用了.NET框架的简单套接字服务器类。它接受对Northwind的employees的电话号码查询,Northwind是一个同Visual Studio .NET一起分发的样例SQL数据库。
240
241表6 简单套接字服务器类
242
243//包含必要的组合件(assemblies)
244
245#using <mscorlib.dll>
246
247#using <system.dll>
248
249#using <system.data.dll>
250
251#using <system.xml.dll>
252
253//打开相关名字空间
254
255using namespace System;
256
257using namespace System::Threading;
258
259using namespace System::Data;
260
261using namespace System::Data::SqlClient;
262
263using namespace System::Collections;
264
265using namespace System::Net::Sockets;
266
267//ok: 这儿是我们的类
268
269__gc class SocketDemo_Server
270
271{
272
273private:
274
275static const int port = 4554;
276
277static const int maxPacket = 128;
278
279TcpListener *tcpl;
280
281DataSet *ds;
282
283DataRowCollection *rows;
284
285public:
286
287SocketDemo_Server();
288
289void Start();
290
291void handleConnection();
292
293//grab the data from the SQL database
294
295void retrieveData();
296
297};
298
299
300表7是运行这个服务器并记录其处理三个客户请求的示例输出。表8则展示了表6中所示的Start函数的实现。Start函数使用线程类来生成独立线程以从数据库中取得数据并处理客户连接。因为WriteLine需要一个引用型的参数,因此必须将port值显式装箱。
301
302表7 服务器输出
303
304Server[4554]: OK: started TcpListener ...
305
306Server[4554]: OK: listening for connections ...
307
308Server[4554]: OK: retrieved SQL database info ...
309
310
311
312Server[4554]: OK: a client connected ...
313
314Server[4554]: OK: client requested phone # for Fuller
315
316Server[4554]: OK: first request for Fuller
317
318Server[4554]: Phone number for Fuller: (206) 555-9482
319
320
321
322Server[4554]: OK: a client connected ...
323
324Server[4554]: OK: client requested phone # for King
325
326Server[4554]: OK: first request for King
327
328Server[4554]: Phone number for King: (71) 555-5598
329
330
331
332Server[4554]: OK: a client connected ...
333
334Server[4554]: OK: client requested phone # for Fuller
335
336Server[4554]: OK: cached request for Fuller
337
338Server[4554]: Phone number for Fuller: (206) 555-9482
339
340
341
342Server[4554]: OK: a client connected ...
343
344Server[4554]: OK: client requested phone # for Musil
345
346Server[4554]: OK: first request for Musil
347
348Server[4554]: Phone number for Musil: Sorry. Cannot be found.
349
350
351
352
353表8 Start函数
354
355void SocketDemo_Server::Start()
356
357{
358
359try
360
361{
362
363tcpl = new TcpListener(port);
364
365tcpl->Start();
366
367Console::WriteLine(S"Server[{0}]: OK: started TcpListener ...", __box( port ));
368
369//从数据库中取得数据 ...
370
371
372---
373|  |
374
375
376
377| 
378---|---|---
379
380
381
382| Still in Love with C++ 中文版(3) |
383---|---
384| |
385
386| |
387
388
389
390|  |
391
392
393
394---|---|---
395| | |
396
397[ 发表日期:2002-4-18 15:48:01 ]
398
399---
400|
401
402
403Thread *tdata = new Thread(new ThreadStart(this, &SocketDemo_Server::retrieveData));
404
405tdata->Start(); //ok: 蹬掉线程 ...
406
407//处理socket连接的线程 ...
408
409Thread *tconnect =
410
411new Thread(new ThreadStart(this, &SocketDemo_Server::handleConnection));
412
413tconnect->Start();
414
415}
416
417catch(Exception *ex)
418
419{
420
421Console::WriteLine(S"Oops: Unable to Set Up SocketDemo_Server");
422
423Console::WriteLine(ex->ToString());
424
425}
426
427}
428
429
430类Exception是.NET异常层次结构的根。ToString方法显示了一个完整的栈跟踪,这酷毙了。ThreadStart是一个委托类型—一种既可指向静态也可指向非静态成员函数的泛型指针。
431
432我可以进一步探究这个实现,但我认为你已经能够得到一个对框架威力和易用性的感性认识了。更为重要的是,你可以看到,通过C++来使用它是多么得easyJ
433
434C++的将来
435
436希望在读完这个对Visual Studio .NET的一瞥后,能够使你打消疑虑、重树信心—Visual C++不但仍是这个家庭中成员之一,并且它还是一个重要的成员!为了突出这个重要性,微软Visual C++小组正努力工作于一个过渡版本,争取尽快交付这些特性。谈及ISO标准C++,这个小组已经以异乎寻常的步幅进行兼容性工作。对于那些牛气的程序员,这意味着模板、模板、模板。大量使用了模板的第三方库,例如Loki和Boost,现在可以在内部编译而不需要兜什么圈子了。正如好莱坞所言:在附近等我。我们不是啥都还没看见吗!
437
438\- 全文完 -
439
440---
441|  |
442
443
444
445| 
446---|---|---
447
448
449
450</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*>