科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道实例解析C++/CLI之静态构造函数

实例解析C++/CLI之静态构造函数

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文主要讲述在CLI的.NET实现中的几个扩展库如何完成这个任务。

作者:中国IT实验室 来源:中国IT实验室 2007年9月14日

关键字: C++ CLI 构造函数

  • 评论
  • 分享微博
  • 分享邮件

就某些类而言,当在程序中第一次使用时,最好能有一个初始化过程;当程序不再需要时,也最好能做一些收尾工作,这些都是非常好的类设计习惯。

引出问题

如果有这样一种情况,某种类型的每个实例都必须有其唯一的ID,比如说某种交易类型,这些ID可用于在处理过程中追踪每笔交易,或之后用于审计员查看数据文件;为讨论方便,此处的ID为从0起始的有符号整型数。

 

如果把一个nextID值保存在内存中,并在每个新实例构造时,把它递增1,这无疑是一个不错的想法,但是,为使在程序连续的执行过程中保持ID值的唯一,就需要在每次程序结束时保存此值,并在下次程序开始运行时恢复这个值,但在标准C++中,是没办法来达到这个目的的,实际上,使用标准CLI库也同样没办法完成。然而,在CLI的.NET实现中有几个扩展库,它们却可以完成这个任务。

问题重现

这回又用到了Point类,因为带有唯一ID的点很适合此主题。例1中的程序输出在代码之后:

例1:

using namespace System;

Point F(Point p) {

 return p;

}

int main()

{

 /*1*/ Point::TraceID = true;

 /*2*/ Point^ hp1 = gcnew Point;

 Console::WriteLine("hp1: {0}", hp1);

 /*3*/ hp1->Move(6,7);

 Console::WriteLine("hp1: {0}", hp1);

 /*4*/ Point^ hp2 = gcnew Point(3,4);

 Console::WriteLine("hp2: {0}", hp2);

 /*5*/ Point p1, p2(-1,-2);

 Console::WriteLine("p1: {0}, p2: {1}", %p1, %p2);

 /*6*/ p1 = F(p2);

 Console::WriteLine("p1: {0}", %p1);

}

输出:

hp1: [0](0,0)

hp1: [0](6,7)

hp2: [1](3,4)

p1: [2](0,0), p2: [3](-1,-2)

p1: [2](-1,-2)

在程序开始运行时,从一个文本文件中读取下一个可用的ID值,并用它来初始化一个Point类中的私有静态(private static)字段。最开始,这个文件包含的值为零。

基于公共静态布尔属性TraceID的值,Point中ToString函数生成的字符串可有选择地包含Point的ID,并以 [id] 的形式作为一个前缀。如果此属性值为true,就包含ID前缀;否则,就不包含。默认情况下,这个属性值被设为false,因此,在标号1中我们把它设为true。

在标号2中,使用默认构造函数为Point分配了内存空间,并显示它的ID为0及值为(0,0)。在标号3中,通过Move函数修改了Point的x与y坐标值,但这不会修改Point的ID,毕竟,它仍是同一个实例--只不过用了不同的值。接着,在标号4中,使用了接受两个参数的构造函数为另一个Point分配了内存空间,并显示它的ID为1及值为(3,4)。

在标号5中创建了两个基于堆栈的实例,并显示出它们的ID及值。在第三个及第四个Point创建时,它们的ID分别为2和3。

在标号6中,p1被赋于了一个新值,然而,p1仍是它之前的同一个Point,所以它的ID没有改变。

第二次运行程序时,输出如下:

hp1: [6](0,0)

hp1: [6](6,7)

hp2: [7](3,4)

p1: [8](0,0), p2: [9](-1,-2)

p1: [8](-1,-2)

如上所示,4个新实例都被赋于了连续的ID值,且与第一次执行时截然不同,但是,还缺少ID 4和5。请留意标号6及函数F的定义,Point参数是传值到此函数的,而一个Point也是通过值返回的。同样地,这两者都会调用到复制构造函数,而其则"忠实"地创建了一个新实例,且每个新实例都有一个唯一的ID。因此,当p2通过值传递时,会创建一个ID为4的临时Point,紧接着,当副本通过值返回时,又会创建一个ID为5的副本,而两个副本都是可丢弃的。当程序结束时,写入到文件中下一个可用的ID为6,而在程序下次运行时,这就是第一个Point在分配空间时将用到的ID。

解决方法

例2中为Point类的修订版本,非常明显,每个实例现在必须包含一个额外的字段(在此为ID),用以保存ID,在此选择的类型为int,虽然标准C++允许其最小为16位,但在CLI环境中,其至少为32位。如果以零开始,那么在ID重复之前,能表示20亿个不同的实例;当然,也能以负20亿开始,那么能表示的范围又将扩展一倍;倘若想要把ID字段再进行扩展,可使用类型long long int,那么至少能有64位,可以创建数不胜数的实例。那么ID为unsigned行吗?如果它的值不会输出到它的父类之外,是可以的,请记住一点,无符号整型与CLS不兼容。(还可选择System::Decimal,其可表示128位。)

例2:

using namespace System;

using namespace System::IO;

public ref class Point

{

 int x;

 int y;

 /*1*/ int ID;

 /*2*/ static int nextAvailableID;

 /*3*/ static int GetNextAvailableID() { return nextAvailableID++; }

 /*4*/ static bool traceID = false;

 /*5*/ static String^ masterFileLocation;

 /*6*/ static Point()

 {

/*6a*/ AppDomain^ appDom = AppDomain::CurrentDomain;

/*6b*/ masterFileLocation = String::Concat(appDom->BaseDirectory,

"\\PointID.txt");

/*6c*/ try {

 /*6d*/ StreamReader^ inStream = File::OpenText(masterFileLocation);

 /*6e*/ String^ s = inStream->ReadLine();

 /*6f*/ nextAvailableID = Int32::Parse(s);

 /*6g*/ inStream->Close();

 /*6h*/ appDom->ProcessExit += gcnew

 EventHandler(&Point::ProcessExitHandler);

}

/*6i*/ catch (FileNotFoundException^ ioFNFEx)

{

 //采取某些必要的措施

}

/*6j*/ finally

{

 appDom = nullptr;

}

 }

 /*7*/ static void ProcessExitHandler(Object^ sender, EventArgs^ e)

 {

/*7a*/ StreamWriter^ outStream = File::CreateText(masterFileLocation);

/*7b*/ outStream->WriteLine("{0}", nextAvailableID);

/*7c*/ outStream->Close();

 }

 public:

 // ...

 /*8*/ static property bool TraceID

 {

bool get() { return traceID; }

void set(bool val) { traceID = val; }

 }

 // define instance constructors

 Point()

 {

/*9*/ ID = GetNextAvailableID();

X = 0;

Y = 0;

 }

 Point(int xor, int yor)

 {

/*10*/ ID = GetNextAvailableID();

X = xor;

Y = yor;

 }

 Point(Point% p) // copy constructor

 {

/*11*/ ID = GetNextAvailableID();

X = p.X;

Y = p.Y;

 }

 // ...

 /*12*/ virtual int GetHashCode() override

 {

// ...

 }

 virtual String^ ToString() override

 {

/*13*/ if (traceID)

{

 return String::Format("[{0}]({1},{2})", ID, X, Y);

}

else

{

 return String::Format("({0},{1})", X, Y);

}

 }

};

一旦作为static,标号2至5中定义的成员属于类,而不属于任何实例;而作为private,它们只是一个实现的细节。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章