科技行者

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

知识库

知识库 安全导航

至顶网软件频道基础软件实例解析C++/CLI的“克隆”

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

  • 扫一扫
    分享文章到微信

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

C++/CLI不但支持基于堆栈的对象,同时也支持基于堆的对象。

作者:谢启东编译 来源:天极开发 2007年11月14日

关键字: 实例 C++ 克隆

  • 评论
  • 分享微博
  • 分享邮件
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);
}
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

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

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