Monday, March 06, 2006

 

Clone Vs. Assign

Consider a scenario where you have an object obj1 of class A pointed (or referenced in today's lingo :-) by an unknown number of other objects. You want to make a [shallow] copy of the obj1 (a clone obj2), change the clone interactively, and when you're satisfied with the result, you want to copy the clone over the original object. Note that since obj1 is currently referenced by an unknown number of other objects, you can't simply modify a [single] reference to obj1 so that it now references obj2: you have to go through all of obj1 fields, and replace them with the corresponding value in obj2 (either that, or you have to update all the references to obj1, which we considered unknown).
Consider how this task could be performed in C++. C++ provides no facilities for cloning, but you can use the copy constructor if class A has one. Not that since we're not making any use of polymorphism, going through a Prototype-pattern-style Clone function would be totally unnecessary: a copy constructor is enough. Later, when you want to copy obj2 over obj1, you can use the assignment operator: C++ doesn't try to hide the difference between an object and a reference (or pointer), so there is no concept of "reference types" and the assignment operator works directly on objects. In many cases, you don't have to define a copy constructor or assignment operator in C++ either, as the compiler can synthesize one for you. Although in C++ 101 you learn that it's often necessary to write your own copy constructor and assignment operator, in many cases a sensible use of smart pointers will make the synthesized version just fine for a shallow copy.
Consider now the same task implemented in C#. There is no notion of copy constructor, but System.Object comes to the rescue and provide a MemberwiseClone method. Since we need a shallow copy, the default implementation of MemberwiseCopy will work just fine, and we don't even have to write a copy-constructor like function. Copying obj2 over obj1, however, is not free. There is no "Assign" function in System.Object. Assigning a reference won't work, and a simple Assign function taking a ref destination parameter won't work either, since obj1 is being referenced by many objects. In this case, I've seen lot of good guys resorting to what I usually call hard work :-). They assign all the fields manually, either inside an A.Assign method or, even worse, in the calling code. Fortunately, there is a better way: in fact, the lack of an Assign method in System.Object is more like a library weakness than a language or platform weakness. Therefore, we can implement our own universal shallow assignment function:
public class ObjectExtension
{
static public void ShallowAssignment( object dest, object source )
{
Type t = dest.GetType() ;
if( t != source.GetType() )
throw new InvalidCastException() ; // may want to be more specific :-)
FieldInfo[] fields = t.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ) ;
foreach( FieldInfo fi in fields )
{
fi.SetValue( dest, fi.GetValue( source ) ) ;
}
}
}
Of course, the class name is not nice, but this kind of function is the living proof that the idea of having each function residing inside a class is flawed, unless we have open classes, in which case ShallowAssign could be a nice addition to System.Object :-).

Comments:
Come l'avrei risolta io...

In astratto avrei immaginato gli oggetti come tante liste "linkate".

Ogni oggetto in sostanza punta alla testa della lista. La testa non contiene dati, ma solo puntatori sx, dx per scorrere a sinistra o a destra la lista circolare doppiamente linkata, ma non è necessario che sia doppia, né circolare: dipende da cosa dobbiamo farci.

Copiare un oggetto equivale a duplicare la lista.

Rimpiazzare l'oggetto originale equivale a liberare (free) gli elementi della lista (tranne la testa) e puntare agli elementi del nuovo (liberando la testa del nuovo).

Un'idea da 2 cents implementabile in C senza autoincremento postfisso.

Prendetela per quello che vale...
 
Lo scenario che proponi mette in luce quella che secondo me e' la differenza piu' sostanziale tra C++ e C#/Java: come hai gia' fatto notare in post precedenti, in C#/Java esiste un certo divario tra cio' che e' il linguaggio e cio' che e' possibile implementare come libreria, metre in C++ esiste una grande liberta' di estensione del linguaggio (e' possibile mantenendo la sintassi estendere a qualunque livello la semantica). C#/Java attraverso le possibilita' riflessive offerte dalla loro virtual machine colmano il lack, ma con un costo in termini di performance ed efficienza (ed eleganza!!) che non sempre e' accettabile. Il C++ e', a mio avviso, ancora lo strumento piu' potente per modellare domini applicativi "la, dove non arrivano le librerie"! Nonostante io lavori per la maggior parte in Java, e' ancora il C++ il mio linguaggio preferito!

Michel
 
Romano:
funziona, solo che... non si puo' fare :-). Gli oggetti in C# sono quello che sono, se ti puntano in N e sei di classe A c'e' poco da fare, mica possiamo trasformare tutto in liste :-).
A latere, serve davvero una lista? Basterebbe un doppio livello di indirezione, che comunque sarebbe solo cautela del programmatore preservare (ovvero non bypassare...)

Michel:
Decisamente d'accordo con quel che dici. Certo se in C++ venisse introdotta una seria reflection, meglio ancora una compile-time reflection su cui basare una run-time reflection, utilizzabile pero' anche all'interno di metaprogrammi template, faremmo un grosso passo avanti...
 
This post has been removed by a blog administrator.
 
This post has been removed by a blog administrator.
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?