C++/CLI: Cloning

C/C++ Users Journal September, 2005

Making copies of heap-based objects

By Rex Jaeschke

Rex Jaeschke is an independent consultant, author, and seminar leader. He serves as editor of the Standards for C++/CLI, CLI, and C#. Rex can be reached at rex@RexJaeschke.com.

Although C++/CLI supports stack-based objects, it also supports those that reside on the heap. However, if you are interoperating with other CLI-based languages (such as C#, Visual Basic, and J#), you need to recognize that they support only heap-based objects. When working in this heap-based object environment, you are always at arms length from an object. Given two handles h1 and h2, *h1 = *h2 only works if an assignment operator has been defined for those handles' type, and for types written in languages other than C++/CLI, which is unlikely to be the case. As such, a CLS-compliant mechanism is needed to create a copy of an object, and that mechanism is called "cloning."

Using a CLI Library Clone Function

Consider Listing 1, which uses the library class System::ArrayList, a vector-like type. Figure 1 is the output.

ArrayList al1 consists of a set of four strings specifying different colors. You then make a complete copy of that object in case 3 by calling the function ArrayList::Clone, so the output produced by cases 2 and 4 are the same.

Then you modify al1 by removing the second element, by adding a new element to the end, and by changing the first element's value. When you compare the output produced by cases 6 and 7, you see that the changes applied to al1 have no affect on al2. Specifically, the internal reference inside of al2 points to its own private copy of elements, not to the same set as al1. This is referred to as a "deep copy," whereas simply making both ArrayLists' internal reference point to the same set of values (as would be the case with the assignment al2 = al1) is called a "shallow copy."

If you wish to make copies of your own objects, you should model the copy process on the library-cloning machinery.

Adding Cloning to a Type

The key to cloning is to implement the standard interface System::ICloneable, which, in turn, requires you to define a function called Clone that takes no arguments and has a return type of System::Object^. The handle returned is to a new object that is a copy of the object on which it is called. For example, consider Listing 2, whose output is:

p1: (3,5)
p1: (9,11)
p2: (3,5)

In case 3, you create the copy by calling Clone, and because that function returns an Object^ (but one that you know refers to a Point), you must cast that to type Point^ before you can assign it to p2. (Even though Point::Clone is really returning a handle to a Point, you can't declare that function as such because that wouldn't satisfy the contract specified by the interface.)

Type System::Object defines a function called MemberwiseClone, as follows:

protected:
    Object^ MemberwiseClone();

This function creates and returns a copy of the object on which it is called. The general intent is that, for any handle x, the following expressions are true:

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

Copying an object typically entails creating a new instance of its class, but it also may require copying of internal data structures as well. No constructors are to be called.

Object::MemberwiseClone performs a specific cloning operation. It creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this function performs a shallow copy of this object.

This implementation of Point uses two instance variables, both of which have the fundamental type int. Fundamental types are value class types, so a shallow copy of one Point to another is sufficient for our needs. You achieve this by calling the base class Object's MemberwiseClone.

Consider class Circle in which a Circle contains a handle to a Point (representing the origin) and a fundamental-typed field (representing the radius); see Listing 3.

The get accessor for the Origin property of class Circle is implemented using Point::Clone. As defined, this accessor returns a Point that is a copy of the center Point.

In case 1, I call Object::MemberwiseClone to make a shallow copy of the Circle. This results in a new Circle whose radius is the same as that of the current one, but has both Circles' origins referring to exactly the same Point. Therefore, in case 2, I call Point::Clone to make sure that the new Circle's origin refers to a copy of the current one's center Point. Finally, in case 3, I return a handle to this new Circle. Listing 4 is a program that tests this class and produces the output shown.

Cloning Arrays

Consider Listing 5, in which I extend the generic class Vector to support cloning. Because all CLI array types derive from System::Array, and that class implements the interface System::ICloneable, you can make a copy of the array elements simply by calling Clone on a reference to that array, as shown. Of course, the cast must include the array notation.

Cloning and Derived Classes

The approach shown thus far works fine for classes that derive directly from Object^; however, it breaks down for classes that derive from other classes. Consider Listing 6. Being derived directly from System::Object, this class's approach to cloning is as seen before. Base is used as the base class for Derived shown in Listing 7.

Stating explicitly that Derived implements ICloneable is redundant; Derived's base class already does that. No new requirement is added.

What is new in this example is the call to Base::Clone in Clone. This replaces our previous call to MemberwiseClone (which has been commented out). By calling Base::Clone (which, in turn, will call its base class's Clone, which, ultimately, will call Object::MemberwiseClone), you get a clone of the base object. Listing 8 is the test program.

Why have I made the Clone functions virtual? Well, when you call Base::Clone, you want to make sure you get the most appropriate implementation, and that is achieved via virtual function lookup.

Creation Without Construction

An implementation of Clone shouldn't call any of its own class's constructors. For most classes, this is not a problem because all their constructors simply initialize the nonstatic data members. However, if a constructor initializes any static data members or performs other operations, these actions will not be done during cloning unless Clone does them itself. In Listing 9, class Test contains a static counter that tracks the number of objects of that type that have been created. Combined, cases 1 and 2 duplicate the behavior of the constructors.

As shown by this output, the object count is incremented when an object is constructed or cloned:

t1 using new: 0, 1
t2 using Clone: 0, 2
t3 using new: 1, 3
t4 using Clone: 1, 4

Reader Exercises

To reinforce the material presented this month, try this:

  1. Implement a generic public class called Matrix that models a two-dimensional table. This type has the skeleton:
  2. generic <typename T>
    public ref class Matrix : ICloneable
    {
         array<T,2>^ values;
    public:
         Matrix(int row, int col);
         property T default[int, int] {
              T get(int row, int col);
              void set(int row, int col, T value);
         }
         virtual Object^ Clone() override;
         virtual String^ ToString() override
    };
    
    
  3. Test the Clone function by creating one Matrix and cloning it, then change one or more elements in each matrix and display the matrices showing that changes to one don't affect the other. q