Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site
Language
English













SourceForge.net Logo



Pick Behaviour Explained

Pick behaviour is really the most "weird" part of U++ and one which is really good to understand.

 

In STL, when you assign a vector variable, the deep copy is performed, i.e. the whole (potentially very complex) object is duplicated. Similarly, when you have a function returning a vector, when you (typically) construct a local variable of the vector type within the function, fill it in and at the end return it as the function value, again a duplication of the whole complex object has to take place under standard C++ semantics. While the former case (assignment) is questionable, it is clear that in many cases the copy in the latter case is a complete waste of time as the local object is going to be destroyed immediately afterwards. There are movements suggesting that a future update of the C++ standard might take care of this by means of a new "temporary reference" modifier, however current C++ compilers don't have any mechanism to cope with this problem.

 

After careful consideration we decided to go the hard way, making it perhaps a little more tricky for the programmer but with the effect of vast time / space savings. The types which are called "pick" have different value transfer mechanism: the destructive copy (also called the pick copy). Note that it doesn't have to do anything with destructors as such: an object which is transferred using the pick copy formally continues to exist (every object belongs somewhere, so the object will be actually destroyed and its destructor called when it comes out of scope), but it is sort of "castrated", i.e. its data can no longer be considered valid. In some situations such objects define a set of methods which can be used even in this "castrated" (i.e. "picked") state; these typically reinitialize the object to an initial state similar to the one after (empty) construction. This is e.g. the case of Vector::Clear and a few more.

 

Anyway, in some specific cases you might need to perform "deep copy" operations even with types that have default "pick" semantics. That is why we have introduced "optional deep copy types" - you can get deep copies of such types (we call such types "pick with deep copy option), but you have to use different operations: the "deep copy constructor" T(const T&, int) and the "deep copy operator" T& <<= (const T&). All U++ containers are "pick types with optional deep copy". Note that that "int" parameter in copy constructor is actually not used and serves only to distinguish that constructor.

 

Important note about composition: If you make a member variable of a pick type within another object, that another object "inherits" the pick type characteristic; by means of standard C++ inheritance mechanisms, its (standard) copy constructor and assignment operator do the pick copy. If you want to implement optional deep copy semantics for your type as well, you can do that by inheriting it like

 

class MyObject : DeepCopyOption<MyObject>

 

(this template defines the <<= operator) and implement for it the deep copy constructor. Let's try it on a simple example:

 

class StateInfo : DeepCopyOption<StateInfo> {

public:

   StateInfo() {}

   StateInfo(const StateInfo& s, int deep)

       : state_names(s.state_names, deep)

       , populations(s.populations, deep)

       , capitals(s.capitals, deep) {}

 

public:

   Vector<String> state_names;

   Vector<double> populations;

   Vector<String> capitals;

};

 

Then when you write in another function, say,

 

StateInfo FilterPopulation(const StateInfo& si, double max_population)

{

   StateInfo s(si, 0);

   for(int i = s.populations.GetCount(); --i;)

       if(s.populations[i] > max_population) {

           s.state_names.Remove(i);

           s.populations.Remove(i);

           s.capitals.Remove(i);

       }

   return s;

}

 

Note the two different types of value transfer: in the beginning, a deep copy is performed (that is necessary in order not to damage the constant argument). Afterwards the filtering is performed in the for() loop and finally the filtered variable s is returned from the function using the pick copy construction (s will no longer be used, so its contents can safely go to hell).

 

Now what makes this so tricky: it is in partial contrast with standards of "polite" behaviour in C++, where you normally expect the = operator to just fill in a new variable, not to wipe out the one on the right side. That is just what happens when you = a pick object. Therefore you must remember which of your objects are of pick type and every time you assign them, copy-construct them or pass them to functions, you must decide whether to use the pick method or the deep method.

 

In practice this is not as hard as it may sound, because typically pick objects are large, complex objects or objects specially optimized for performance, and when you optimize for performance, you usually care about what's going on during their manipulation anyway. However, in certain cases you want to use a pick type object without having to care about the picks: in such situations you can use the WithDeepCopy<> modifier, which wraps a pick type object with deep copy option in such a way it starts to behave as the ordinary objects with default deep copy semantics.

Last edit by unodgs on 08/20/2006. Do you want to contribute?. T++