ultimatepp/uppsrc/Core/srcdoc.tpp/Moveable$en-us.tpp
cxl 2a938d650d .docs
git-svn-id: svn://ultimatepp.org/upp/trunk@7049 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2014-03-16 18:19:08 +00:00

243 lines
No EOL
10 KiB
C++

topic "Moveable";
[2 $$0,0#00000000000000000000000000000000:Default]
[l288;i704;a17;O9;~~~.992;2 $$1,0#10431211400427159095818037425705:param]
[a83;*R6 $$2,5#31310162474203024125188417583966:caption]
[b83;*2 $$3,5#07864147445237544204411237157677:title]
[b167;a42;C2 $$4,6#40027414424643823182269349404212:item]
[b42;a42;2 $$5,5#45413000475342174754091244180557:text]
[l288;a17;2 $$6,6#27521748481378242620020725143825:desc]
[l321;t246;C@5;1 $$7,7#20902679421464641399138805415013:code]
[b2503;2 $$8,0#65142375456100023862071332075487:separator]
[*@(0.0.255)2 $$9,0#83433469410354161042741608181528:base]
[t4167;C2 $$10,0#37138531426314131251341829483380:class]
[l288;a17;*1 $$11,11#70004532496200323422659154056402:requirement]
[i417;b42;a42;O9;~~~.416;2 $$12,12#10566046415157235020018451313112:tparam]
[b167;C2 $$13,13#92430459443460461911108080531343:item1]
[i288;a42;O9;C2 $$14,14#77422149456609303542238260500223:item2]
[*@2$(0.128.128)2 $$15,15#34511555403152284025741354420178:NewsDate]
[l321;*C$7;2 $$16,16#03451589433145915344929335295360:result]
[l321;b83;a83;*C$7;2 $$17,17#07531550463529505371228428965313:result`-line]
[l160;t4167;*C+117 $$18,5#88603949442205825958800053222425:package`-title]
[{_}%EN-US
[s2; Moveable&]
[s5; First important node: U`+`+ Moveable is not to be confused with
C`+`+ standard library move semantics.&]
[s5; Moveable concept represents basic requirement for types stored
in Vector flavor of containers (namely Vector, BiVector, Index,
VectorMap, InVector, SortedIndex, SortedVectorMap). To explain
what it is and why it is so important let us first create a very
primitive Vector`-like container template&]
[s7; template <class T>&]
[s7; class SimpleVector `{&]
[s7; -|T `*vector;&]
[s7; -|int capacity;&]
[s7; -|int items;&]
[s7; &]
[s7; -|void [* Expand()] `{&]
[s7; -|-|capacity `= max(1, 2 `* capacity);&]
[s7; -|-|T `*newvector `= (T `*) new char`[capacity `* sizeof(T)`];&]
[s7; -|-|for(int i `= 0; i < items; i`+`+) `{&]
[s7; -|-|-|[* new(newvector`[i`]) T(vector`[i`])];&]
[s7; -|-|-|[* vector`[i`].T`::`~T();]&]
[s7; -|-|`}&]
[s7; -|-|delete`[`] (char `*) vector;&]
[s7; -|-|vector `= newvector;&]
[s7; -|`}&]
[s7; public:&]
[s7; -|void Add(const T`& x) `{&]
[s7; -|-|if(items >`= capacity) [* Expand()];&]
[s7; -|-|new(vector`[items`+`+`]) T(x);&]
[s7; -|`}&]
[s7; -|T`& operator`[`](int i) `{ return vector`[i`]; `}&]
[s7; -|SimpleVector() `{&]
[s7; -|-|vector `= NULL;&]
[s7; -|-|capacity `= items `= 0;&]
[s7; -|`}&]
[s7; -|`~SimpleVector() `{&]
[s7; -|-|for(int i `= 0; i < items; i`+`+)&]
[s7; -|-|-|vector`[i`].T`::`~T();&]
[s7; -|-|delete`[`] (char `*)vector;&]
[s7; -|`}&]
[s7; `};&]
[s5; This [* SimpleVector] stores added elements in the [* vector] member
field. If there is no more empty storage space left in [* vector],
[* SimpleVector] simply doubles its capacity using [* Expand] method.
This method is what interests us `- because [* Expand ]requires
means to copy values of elements from the original memory area
to the newly allocated one. The version above uses placement
new and copy constructor for this purpose. This also means that
[* SimpleVector] requires T to have copy constructor (in standard
C`+`+ library terms: it must be [/ copy`-constructible]). Now let
us create a typical element that can be stored in such container&]
[s7; class SimpleString `{&]
[s7; -|char `*text;&]
[s7; public:&]
[s7; -|SimpleString(const char `*txt) `{&]
[s7; -|-|text `= new char`[strlen(txt)`+1`];&]
[s7; -|-|strcpy(text, txt);&]
[s7; -|`}&]
[s7; -|SimpleString(const SimpleString`& s) `{&]
[s7; -|-|text `= new char`[strlen(s.text)`+1`];&]
[s7; -|-|strcpy(text, s.text);&]
[s7; -|`}&]
[s7; -|void operator`=(const SimpleString`& s) `{&]
[s7; -|-|delete`[`] text;&]
[s7; -|-|text `= new char`[strlen(s.text)`+1`];&]
[s7; -|-|strcpy(text, s.text);-|-|&]
[s7; -|`}&]
[s7; -|`~SimpleString() `{&]
[s7; -|-|delete`[`] text;&]
[s7; -|`}&]
[s7; `};&]
[s5; and see what happens when [* SimpleVector] of [* SimpleString]s
is expanded: First, copies of all elements are created, that means
allocating new storage for [* text] member of new element and copying
source [* text] to it using [* strcpy]. A moment later, [* Expand]
invokes destructor for element, thus deleting all [* text]s in
the original elements. Does not it seem we are wasting a lot
of CPU cycles just to make copies of things that we throw away
a moment later anyway? What if instead of making copies we could
find a way to transfer original elements`' [* text] members to
new elements and somehow disallow [* delete`[`] text] in destructor?
See how primitive it can be:&]
[s7; template <class T>&]
[s7; class SimpleVector `{&]
[s7; -|T `*vector;&]
[s7; -|int capacity;&]
[s7; -|int items;&]
[s7; &]
[s7; -|void Expand() `{&]
[s7; -|-|capacity `= max(1, 2 `* capacity);&]
[s7; -|-|T `*newvector `= (T `*) new char`[capacity `* sizeof(T)`];&]
[s7; -|-|[* memcpy(newvector, vector, items `* sizeof(T));]&]
[s7; -|-|delete`[`](char `*)vector;&]
[s7; -|-|vector `= newvector;&]
[s7; -|`}&]
[s7; public:&]
[s7; -|void Add(const T`& x) `{&]
[s7; -|-|if(items >`= capacity) Expand();&]
[s7; -|-|new(vector`[items`+`+`]) T(x);&]
[s7; -|`}&]
[s7; -|SimpleVector() `{&]
[s7; -|-|vector `= NULL;&]
[s7; -|-|capacity `= items `= 0;&]
[s7; -|`}&]
[s7; -|`~SimpleVector() `{&]
[s7; -|-|for(int i `= 0; i < items; i`+`+)&]
[s7; -|-|-|vector`[i`].T`::`~T();&]
[s7; -|-|delete`[`] (char `*)vector;&]
[s7; -|`}&]
[s7; `};&]
[s5; For the moment please ignore fact that by using memcpy to move
non`-POD types we are violating C`+`+ standard (we will discuss
it later). Now, what we get here is exactly what we wanted `-
instead of a series of copy construction and destruction we simply
copy raw binary data to the new location. This way we simply
transfer the old value in the [* text ]member of elements into
the new expanded [* vector]. We need to invoke neither copy constructor
nor destructor when expanding vector. Not a single CPU cycle
is lost.&]
[s5; The types that can be moved in memory using memcpy are called
[*/ moveable.]&]
[s5; Clearly not all types are moveable. Being moveable has a lot
to do with [/ not] storing references to the object itself or to
it`'s parts. E.g.&]
[s7; struct Link `{&]
[s7; -|Link `*prev;&]
[s7; public:&]
[s7; -|Link() `{ prev `= NULL; `}&]
[s7; -|Link(Link `*p) `{ prev `= p; `}&]
[s7; `};&]
[s5; is [* not] moveable, because memcpy`-ing to a new location would
break the existing links. All of the following requirements should
be fullfilled for moveable types:&]
[s5;i150;O0; It does not have any virtual method nor virtual base
class.&]
[s5;i150;O0; Base classes (if any) and any instance member variables
are moveable.&]
[s5;i150;O0; No references or pointers are stored to the object itself
or to subobjects in the methods of the type, into variables that
exist after the method finishes execution. &]
[s5; Fundamental types fulfills these requirements so they are moveable.&]
[s5; Example:&]
[s7; struct Foo;&]
[s7; &]
[s7; Foo `*global`_foo;&]
[s0; &]
[s7; struct Foo `{&]
[s7; -|int a;&]
[s7; -|Foo `*foo;&]
[s7; -|int `*ptr;&]
[s7; public:&]
[s7; -|void Set(Foo `* f) `{&]
[s7; -|-|foo `= f;&]
[s7; -|`}&]
[s7; -|void Ok1() `{&]
[s7; -|-|Foo `*x `= this;&]
[s7; -|// local variable will not exist outside method&]
[s7; -|// `-> does not prevent Foo from being moveable&]
[s7; -|`}&]
[s7; -|void Ok2() `{&]
[s7; -|-|memset(`&a, 0, sizeof(int));&]
[s7; -|// pointer will not exist outside method&]
[s7; -|// `-> does not prevent Foo from being moveable&]
[s7; -|`}&]
[s7; -|void Bad1() `{&]
[s7; -|-|foo `= this;&]
[s7; -|// member variable foo exists outside method&]
[s7; -|// `-> makes Foo non`-moveable&]
[s7; -|`}&]
[s7; -|void Bad2() `{&]
[s7; -|-|ptr `= `&a;&]
[s7; -|// pointer to subobject stored, ptr exists outside method&]
[s7; -|// `-> makes Foo non`-moveable&]
[s7; -|`}&]
[s7; -|void Bad3() `{&]
[s7; -|-|global`_foo `= this;&]
[s7; -|// pointer stored to global variable&]
[s7; -|// `-> makes Foo non`-moveable&]
[s7; -|`}&]
[s7; -|void Bad4(Foo`& another) `{&]
[s7; -|-|another.Set(this);&]
[s7; -|// pointer stored indirectly in object that exist outside method&]
[s7; -|// `-> makes Foo non`-moveable&]
[s7; -|`}&]
[s7; `};&]
[s5; These requirements satisfies fairly large number of types, incidentally
most of the types you ever wanted to store in a container of
elements of a single type are moveable. Most important, all NTL
containers [* are ]moveable.&]
[s5; Now we have an effective method how to organize the storing
of elements in containers. We have to deal with the fact that
being moveable is part of an object`'s interface, and we should
ensure that only movable elements are stored into NTL containers.
For this we need a way to declare at compile time that a certain
type is moveable and also a way to check it.&]
[s5; To achieve this goal, you can mark moveable types by deriving
them from the [* Moveable] template class e.g.:&]
[s7; class SimpleString : Moveable<SimpleString> `{ ... `}&]
[s5; Alternatively the [* NTL`_MOVEABLE ]macro can be used to mark
types as moveable if the class interface can not be changed,
such as in:&]
[s7; NTL`_MOVEABLE(std`::string);&]
[s5; Now that we can mark types as moveable, we need a way to check
a type for being moveable. This is done by adding the line:&]
[s7; AssertMoveable<T>()&]
[s5; to one of the methods of a template that gets compiled for any
template argument `- the destructor is the most obvious place.
The AssertMovable function is defined only if [* T] is marked as
moveable, so it results in compilation error for non`-moveable
T types.&]
[s5; Finally, time has come to deal with the C`+`+ standard. Current
C`+`+ defines memcpy only for POD types. The moveable concept
requires memcpy of non`-POD types to be defined. In fact, difference
between POD and moveable non`-POD types is the existence of constructors
and non`-virtual destructor. To get things work, all we need
is that the result of memcpy`-ing non`-POD type [* T ]is same as
memcpy`-ing the POD [* T1 ]type which you would get by removing
the destructor and the constructors from [* T]. While this operation
is still undefined by C`+`+, it is really hard to imagine an
optimal C`+`+ implementation that would break this rule. Indeed,
all current implementation we have met so far support moveable
semantics in the way we have defined here. Performance and other
gains realized by using the moveable concept are too big to ignore.]]