分享
 
 
 

实例解析C++/CLI的“克隆”

王朝c/c++·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

C++/CLI不但支持基于堆栈的对象,同时也支持基于堆的对象;然而,假如想与其他基于CLI的语言(如C#、J#、Visual Basic)进行互操作的话,必须要清楚地知道,这些语言只支持基于堆的对象;当处于基于堆的对象环境中时,你与对象之间,永远只有"一臂之遥",比方说,两个给定的句柄h1与h2,只有在为这种句柄类型定义了相应的赋值操作符时,*h1 = *h2才会工作正常,而对C++/CLI之外的其他语言中的类型来说,情况可能就不是这样了。同样地,一个遵从CLS的机制需要创建对象的一份副本,这种机制被称为"克隆"。

使用CLI库中的Clone函数

请看例1中的代码,其使用了类似于矢量的一个System::ArrayList类,插1是程序的输出。

例1:

using namespace System;

using namespace System::Collections;

void PrintEntries(String^ s, ArrayList^ aList);

int main()

{

ArrayList^ al1 = gcnew ArrayList;

/*1*/ al1->Add("Red");

al1->Add("Blue");

al1->Add("Green");

al1->Add("Yellow");

/*2*/ PrintEntries("al1", al1);

/*3*/ ArrayList^ al2 = static_cast<ArrayList^>(al1->Clone());

/*4*/ PrintEntries("al2", al2);

/*5*/ al1->Remove("Blue");

al1->Add("Black");

al1->RemoveAt(0);

al1->Insert(0, "Brown");

/*6*/ PrintEntries("al1", al1);

/*7*/ PrintEntries("al2", al2);

}

void PrintEntries(String^ s, ArrayList^ aList)

{

Console::Write("{0}: ", s);

for each(Object^ o in aList)

{

Console::Write("\t{0}", o);

}

Console::WriteLine();

}

插1:程序输出

al1: Red Blue Green Yellow

al2: Red Blue Green Yellow

al1: Brown Green Yellow Black

al2: Red Blue Green Yellow

ArrayList al1由4个代表不同颜色的字符串组成,通过在标记3中调用ArrayList::Clone函数,可以对此对象作一个完整的复制,所以,标记2与4表示的输出完全相同。

接下来,从al1中移除了第二个元素,在末尾加入了一个新的元素,并修改了第一个元素的值。当把标记6与7表示的输出进行一个对比时,你会发现,对al1所作的修改,完全不会影响到al2。在此需要说明的是,al2内部的引用,指向其自身元素的私有副本,而不是al1中的元素,这就是通常提到的"深拷贝",反之,只是简单地把两个ArrayList内部引用指向同一个值集(如al2=al1的赋值操作),这称为"浅拷贝"。

也就是说,假如你希望复制所拥有的对象,应该参照库函数Clone机制中的复制过程。

在类型中添加克隆

克隆的要害是实现System::ICloneable标准接口,其需要你定义一个调用Clone、不接受任何参数、并带有一个System::Object^返回类型的函数,返回的句柄指向一个新的对象,这个对象是被调用对象的一个副本。请看例2:

例2:

public ref class Point : ICloneable

{

// ...

public:

virtual Object^ Clone()

{

return MemberwiseClone();

}

};

int main()

{

/*1*/ Point^ p1 = gcnew Point(3, 5);

/*2*/ Console::WriteLine("p1: {0}", p1);

/*3*/ Point^ p2 = static_cast<Point^>(p1->Clone());

/*4*/ p1->Move(9, 11);

/*5*/ Console::WriteLine("p1: {0}", p1);

/*6*/ Console::WriteLine("p2: {0}", p2);

}

以下是程序的输出:

p1: (3,5)

p1: (9,11)

p2: (3,5)

在标记3中,通过调用Clone进行了复制,而因为此函数返回一个Object^类型的值(在此为一个Point的引用),在把它赋值给p2之前,必须转换为一个Point^。(即便Point::Clone真的返回一个Point的句柄,也不能这样声明函数,因为不符合接口规范。)

在类型System::Object中定义了一个名为MemberwiseClone的函数,如下所示:

protected:

Object^ MemberwiseClone();

这个函数创建并返回对象的一份副本,而一般的用法是,对任意句柄x,以下的表达式都为真:

x->MemberwiseClone() != x

x->MemberwiseClone()->GetType() == x->GetType()

通常来说,复制一个对象必须创建对象的一个新实例,但同时也可能需要对内部数据结构进行复制,在此不需要调用任何的构造函数。

Object::MemberwiseClone执行一个具体而精确的克隆操作。它创建类对象的一个新实例,并用源对象字段内容,初始化对应的所有字段,就似乎在赋值;但是要注重的是,字段内容本身并没有被克隆,所以,这个函数执行的是一个对象的"浅拷贝"。

在Point的实现中,使用了两个实例变量,两者均具有基本类型int。基本类型就是值类型,所以对一个Point的浅拷贝已经完全能满足我们的需要,也就是通过调用基类对象的MemberwiseClone来完成的。

下面来看一个Circle类,其包含了一个指向Point的句柄(表示原始位置)和一个基本类型字段(表示radius半径);见例3:

例3:

using namespace System;

public ref class Circle : ICloneable

{

Point^ origin;

float radius;

public:

property Point^ Origin

{

Point^ get() { return static_cast<Point^>(origin->Clone()); }

}

void SetOrigin(int x, int y)

{

origin->X = x;

origin->Y = y;

}

void SetOrigin(Point^ p)

{

SetOrigin(p->X, p->Y);

}

property double Radius

{

double get() { return radius; }

void set(double value) {

radius = static_cast<float>(value);

}

}

Circle()

{

origin = gcnew Point;

SetOrigin(0, 0);

Radius = 0.0;

}

Circle(int x, int y, double r)

{

origin = gcnew Point;

SetOrigin(x, y);

Radius = r;

}

Circle(Point^ p, double r)

{

origin = gcnew Point;

SetOrigin(p->X, p->Y);

Radius = r;

}

virtual String^ ToString() override

{

return String::Concat("{", Origin, ",", Radius, "}");

}

virtual Object^ Clone()

{

/*1*/ Circle^ c = static_cast<Circle^>(MemberwiseClone());

/*2*/ c->origin = static_cast<Point^>(origin->Clone());

/*3*/ return c;

}

};

Circle类中Origin属性的get方法由Point::Clone来实现,如定义中所示,其返回一个Point中心点的Point副本。

在标记1中,调用了Object::MemberwiseClone以对Circle进行了一次浅拷贝,新Circle中的radius就与当前值一样了,并且两个Circle中的origin均引用同一个Point;因此,在标记2中,调用了Point::Clone以确保新Circle的origin引用为一个当前Point中心点的副本;最后,在标记3中,返回了这个新Circle的句柄。例4是测试这个类的程序:

例4:

int main()

{

/*1*/ Circle^ c1 = gcnew Circle(5, 9, 1.5);

/*2*/ Console::WriteLine("c1: {0}", c1);

/*3*/ Circle^ c2 = static_cast<Circle^>(c1->Clone());

/*4*/ Point^ p = c1->Origin;

/*5*/ Console::WriteLine(" p: {0}", p);

/*6*/ c1->SetOrigin(9, 11);

/*7*/ Console::WriteLine("c1: {0}", c1);

/*8*/ Console::WriteLine(" p: {0}", p);

/*9*/ Console::WriteLine("c2: {0}", c2);

}

克隆数组

请看例5,其扩展了泛型类Vector以支持克隆。因为所有的CLI数组类型均派生自System::Array,而其也实现了System::ICloneable接口,所以可以对一个数组的引用,简单地调用Clone以复制数组中的元素,如例子中所示。当然,在转换中也必须包括数组符号。

例5:

generic <typename T>

public ref class Vector

{

int length;

/*1*/ array<T>^ vector;

public:

virtual Object^ Clone()

{

Vector<T>^ v = static_cast<Vector<T>^>(MemberwiseClone());

v->vector = static_cast<array<T>^>(vector->Clone());

return v;

}

};

int main()

{

/*1*/ Vector<int>^ v1 = gcnew Vector<int>(5, 7);

/*2*/ Console::WriteLine("v1: {0}", v1);

/*3*/ Vector<int>^ v2 = static_cast<Vector<int>^>(v1->Clone());

/*4*/ Console::WriteLine("v2: {0}", v2);

/*5*/ v1[0] = 3;

/*6*/ v1[3] = 9;

/*7*/ v2[4] = 1;

/*8*/ Console::WriteLine("v1: {0}", v1);

/*9*/ Console::WriteLine("v2: {0}", v2);

}

克隆并派生类

到目前为止,所演示的方法对直接从Object^派生而来的类来说,都完全正确,然而,对从其他类派生而来的类来说,就不正确了。请看例6,因为是直接从System::Object派生而来,所以它的克隆方法正如前面所看到的那样。Base被用作基类,而例7中的程序由其派生而来:

例6:

public ref class Base : ICloneable

{

array<int>^ bPair ;

public:

Base(int i, int j)

{

bPair = gcnew array<int>(2) {i, j};

}

void SetValue(int i, int j)

{

bPair[0] = i;

bPair[1] = j;

}

virtual String^ ToString() override

{

return String::Concat("[", bPair[0], ":", bPair[1], "]");

}

virtual Object^ Clone() override

{

Base^ b = static_cast<Base^>(MemberwiseClone());

b->bPair = static_cast<array<int>^>(bPair->Clone());

return b;

}

};

例7:

using namespace System;

public ref class Derived : Base, ICloneable

{

array<int>^ dPair;

public:

Derived(int bi, int bj, int i, int j) : Base(bi, bj)

{

dPair = gcnew array<int>(2) {i, j};

}

void SetValue(int bi, int bj, int i, int j)

{

Base::SetValue(bi, bj);

dPair[0] = i;

dPair[1] = j;

}

virtual String^ ToString() override

{

return String::Concat("[{", Base::ToString(), "}", dPair[0], ":", dPair[1], "]");

}

virtual Object^ Clone() override

{

// Derived^ d = static_cast<Derived^>(Base::MemberwiseClone());

Derived^ d = static_cast<Derived^>(Base::Clone());

d->dPair = static_cast<array<int>^>(dPair->Clone());

return d;

}

};

在Derived中显示声明实现ICloneable是多余的,因为Derived的基类已经这样做了。而这个例子中唯一新的东西就是在Clone中调用Base::Clone,这取代了前一个对MemberwiseClone的调用(其已被注释)。通过调用Base::Clone(其会调用基类的Clone,而它又最终调用Object::MemberwiseClone),这做就得到了一个基类对象的克隆。例8是测试程序。

例8:

int main()

{

Derived^ d1 = gcnew Derived(10, 20, 30, 40);

Console::WriteLine("d1 = {0}", d1);

Derived^ d2 = static_cast<Derived^>(d1->Clone());

Console::WriteLine("d2 = {0}", d2);

d1->Base::SetValue(5, 6);

Console::WriteLine("d1 = {0}", d1);

Console::WriteLine("d2 = {0}", d2);

}

在此,为什么要让Clone成为一个虚拟函数呢?因为,当调用Base::Clone时,必须确保调用了最恰当的实现,而虚拟函数查找,正是实现此的良方。

无构造的创建

一个对Clone的实现,不应调用任何其自身的类构造函数,对大多数类而言,这都不是问题,因为所有它们的构造函数只是初始化非静态的数据成员,然而,假如一个构造函数初始化了任意的静态数据成员或执行了其他的操作,这些操作在克隆期间就不会被完成,除非Clone本身执行了这些操作。在例9中,类Test包含了一个静态计数器,其跟踪类型对象被创建的数目;标记1与2执行了与构造函数同样的操作。

例9:

public ref class Test : ICloneable

{

int data;

static int objectCount = 0;

public:

Test()

{

data = 0;

++objectCount;

}

Test(int value)

{

data = value;

++objectCount;

}

virtual String^ ToString() override

{

return String::Concat(data, ", ", objectCount);

}

virtual Object^ Clone()

{

/*1*/ Test^ copy = static_cast<Test^>(MemberwiseClone());

/*2*/ ++objectCount;

return copy;

}

};

int main()

{

/*3*/ Test^ t1 = gcnew Test;

Console::WriteLine("t1 using new: {0}", t1);

/*4*/ Test^ t2 = static_cast<Test^>(t1->Clone());

Console::WriteLine("t2 using Clone: {0}", t2);

/*5*/ Test^ t3 = gcnew Test(1);

Console::WriteLine("t3 using new: {0}", t3);

/*6*/ Test^ t4 = static_cast<Test^>(t3->Clone());

Console::WriteLine("t4 using Clone: {0}", t4);

}

}

以下是程序输出:

t1 using new: 0, 1

t2 using Clone: 0, 2

t3 using new: 1, 3

t4 using Clone: 1, 4

由输出可见,当一个对象被构造或被克隆时,对象计数都相应地增长了。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有