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<NotQuery*>(nq->clone());
有了对协变返回类型的支持,你可以显式声明NotQuery的clone返回一个NotQuery*:
//有了协变返回类型支持...
virtual NotQuery* clone() {return new NotQuery(this);}
这就允许你用直觉的方式来使用clone,而无需显式造型转换:
//ok: 隐式转换...
Query *pq = nq->clone();
//ok: 无需转换...
NotQuery *pnq = nq->clone();
尽管静态整型常量成员的显式初始化并不象协变返回类型这样的语言特性来得那么紧要,但在类需要常量表达式的情况下,它提供了重大的设计便利,比如用于固定尺寸的数组定义中。例如:
class Buffer
{
static const int ms_buf_size = 1024;
char m_buffer[ms_buf_size];
};
静态整型常量成员是C++中唯一可在类定义中被显式初始化的类成员,从而使得该成员的值可被类中的其它成分使用,比如m_buffer的维数尺寸。否则,一般使用枚举作为符号常量代替。
main的返回值表示程序的退出状态。约定俗成,0表示程序成功退出。对于标准C++,没有显式返回值的main意味着编译器将在其尾部之前插入一行return 0。Visual Studio .NET中的Visual C++终于遵从了这个标准。
Visual Studio .NET中受管制的C++
Visual Studio .NET中C++受管制的扩展主要考虑以下三个应用策略:
l 对于不受管制的API,提供了受管制的.NET包装类,从而可将现存的C++类暴露给微软.NET平台。
l 可以使用微软.NET类框架,并可与不受管制的C++混用。对框架来说有三个方面:核心语言支持,例如集合类和系统I/O;基础编程类,如对线程、网络套接字和正则表达式的支持;应用领域支持,例如XML、ASP.NET、Web Services、Windows Forms和ADO.NET等等。
l 你可以直接在.NET环境中编写,就好比在C#和Visual Basic中一样。不过,这个版本中的C++还未提供对RAD设计者的支持,例如Windows Forms和Web Forms。
首先,我将讨论包装一个不受管制的实现。在我的《C++ Primer》第三版(Addison-Wesley, 1998)一书里,我创建了一个比较大的文本查询应用,它着重练习STL容器类,用以解析文本文件并建立内部表示。表1展示了包含文件,表2展示了数据表示。
表1 文本查询应用的包含文件
#include <algorithm>
#include <string>
#include <vector>
#include <utility>
#include <map>
#include <set>
#include <iostream>
#include <fstream>
#include <stddef.h>
#include <ctype.h>
using namespace std;
表2数据表示
typedef pair<short,short> location;
typedef vector<location> loc;
typedef vector<string> text;
typedef pair<text*,loc*> text_loc;
class TextQuery
{
public:
// ...
private:
vector<string> *lines_of_text;
text_loc *text_locations;
map<string,loc*> *word_map;
Query *query;
static string filt_elems;
vector<int> line_cnt;
};
Query是一个解释查询语言的面向对象层次结构的抽象基类。表3展示了一个查询会话的可能运行方式。文本查询系统的一个本地调用看起来和下面相似:
int main()
{
TextQuery tq;
tq.build_up_text();
tq.query_text();
}
表3查询会话
Enter a query-please separate each item by a space.
Still in Love with C++ 中文版(2)
[ 发表日期:2002-4-18 15:47:42 ]
Terminate query (or session) with a dot( . ).
==> fiery && ( bird || shyly )
fiery ( 1 ) lines match
bird ( 1 ) lines match
shyly ( 1 ) lines match
( bird || shyly ) ( 2 ) lines match
fiery && ( bird || shyly ) ( 1 ) lines match
Requested query: fiery && ( bird || shyly )
( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,
我希望无需做什么修修补补的事情(更不要说重新实现它了)就可将TextQuery接口暴露给.NET平台。毕竟,它能够工作。我心里没底—是不是如果我想移到.NET平台就意味着我必须放弃在本地代码上的投资。幸运地是,包装本地代码以暴露给.NET平台是受管制的C++的拿手好戏。表4展示了干这种事的代码的可能模样。
表4 包装本地C++类
#include "TextQuery.h"
__gc class TextQueryNet
{
private:
TextQuery *pquery;
public:
TextQueryNet() : pquery(new TextQuery()){}
~TextQueryNet(){delete pquery;}
void query_text(){pquery->query_text();}
void build_up_text(){pquery->build_up_text();}
// ...
};
__gc修饰符(见表4所示)将该类标记为一个受管制的类—一个配置在CLR受管制堆上的被垃圾收集的类。我将本地类声明为指针成员,利用表达式new将其配置在不受管制的堆上,就象我在本地代码里做的一样。因为它不被垃圾收集,我在析构器里将它delete掉。build_up_text和query_text都充当存根函数,将调用调度到实际的TextQuery对象。
表5展示了修改过的main函数,它是由受管制的C++项目向导生成的。
表5 生成的main函数
#include "stdafx.h"
#using <mscorlib.dll>
#include <tchar.h>
using namespace System;
#include "gc_TextQuery.h"
int _tmain(void)
{
Console::WriteLine(S"Beginning managed wrapper test ...");
TextQueryNet *tqn = new TextQueryNet();
tqn->build_up_text();
tqn->query_text();
Console::WriteLine(S"Ending managed wrapper test ...");
return 0;
}
现在让我们来讨论如何使用.NET框架。假如有人问我ISO标准C++最严重的缺点是什么,我会说是这样的一个事实—没有诸如线程、网络编程、正则表达式和XML等编程领域的标准库。标准委员会计划在下一轮时间里弥补这个不足,但离现在还有几年,这期间你怎么办?幸运地是,.NET框架提供了一个颇具吸引力的解决方案。例如,表6展示了一个使用了.NET框架的简单套接字服务器类。它接受对Northwind的employees的电话号码查询,Northwind是一个同Visual Studio .NET一起分发的样例SQL数据库。
表6 简单套接字服务器类
//包含必要的组合件(assemblies)
#using <mscorlib.dll>
#using <System.dll>
#using <System.Data.dll>
#using <System.Xml.dll>
//打开相关名字空间
using namespace System;
using namespace System::Threading;
using namespace System::Data;
using namespace System::Data::SqlClient;
using namespace System::Collections;
using namespace System::Net::Sockets;
//ok: 这儿是我们的类
__gc class SocketDemo_Server
{
private:
static const int port = 4554;
static const int maxPacket = 128;
TcpListener *tcpl;
DataSet *ds;
DataRowCollection *rows;
public:
SocketDemo_Server();
void Start();
void handleConnection();
//grab the data from the SQL database
void retrieveData();
};
表7是运行这个服务器并记录其处理三个客户请求的示例输出。表8则展示了表6中所示的Start函数的实现。Start函数使用线程类来生成独立线程以从数据库中取得数据并处理客户连接。因为WriteLine需要一个引用型的参数,因此必须将port值显式装箱。
表7 服务器输出
Server[4554]: OK: started TcpListener ...
Server[4554]: OK: listening for connections ...
Server[4554]: OK: retrieved SQL database info ...
Server[4554]: OK: a client connected ...
Server[4554]: OK: client requested phone # for Fuller
Server[4554]: OK: first request for Fuller
Server[4554]: Phone number for Fuller: (206) 555-9482
Server[4554]: OK: a client connected ...
Server[4554]: OK: client requested phone # for King
Server[4554]: OK: first request for King
Server[4554]: Phone number for King: (71) 555-5598
Server[4554]: OK: a client connected ...
Server[4554]: OK: client requested phone # for Fuller
Server[4554]: OK: cached request for Fuller
Server[4554]: Phone number for Fuller: (206) 555-9482
Server[4554]: OK: a client connected ...
Server[4554]: OK: client requested phone # for Musil
Server[4554]: OK: first request for Musil
Server[4554]: Phone number for Musil: Sorry. Cannot be found.
表8 Start函数
void SocketDemo_Server::Start()
{
try
{
tcpl = new TcpListener(port);
tcpl->Start();
Console::WriteLine(S"Server[{0}]: OK: started TcpListener ...", __box( port ));
//从数据库中取得数据 ...
Still in Love with C++ 中文版(3)
[ 发表日期:2002-4-18 15:48:01 ]
Thread *tdata = new Thread(new ThreadStart(this, &SocketDemo_Server::retrieveData));
tdata->Start(); //ok: 蹬掉线程 ...
//处理socket连接的线程 ...
Thread *tconnect =
new Thread(new ThreadStart(this, &SocketDemo_Server::handleConnection));
tconnect->Start();
}
catch(Exception *ex)
{
Console::WriteLine(S"Oops: Unable to Set Up SocketDemo_Server");
Console::WriteLine(ex->ToString());
}
}
类Exception是.NET异常层次结构的根。ToString方法显示了一个完整的栈跟踪,这酷毙了。ThreadStart是一个委托类型—一种既可指向静态也可指向非静态成员函数的泛型指针。
我可以进一步探究这个实现,但我认为你已经能够得到一个对框架威力和易用性的感性认识了。更为重要的是,你可以看到,通过C++来使用它是多么得easyJ
C++的将来
希望在读完这个对Visual Studio .NET的一瞥后,能够使你打消疑虑、重树信心—Visual C++不但仍是这个家庭中成员之一,并且它还是一个重要的成员!为了突出这个重要性,微软Visual C++小组正努力工作于一个过渡版本,争取尽快交付这些特性。谈及ISO标准C++,这个小组已经以异乎寻常的步幅进行兼容性工作。对于那些牛气的程序员,这意味着模板、模板、模板。大量使用了模板的第三方库,例如Loki和Boost,现在可以在内部编译而不需要兜什么圈子了。正如好莱坞所言:在附近等我。我们不是啥都还没看见吗!
- 全文完 -