Reformulated the placement new operator section

git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@540 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
Frank B. Brokken 2011-05-27 13:44:34 +00:00
parent c253f075c8
commit cd5ca4aa1f
5 changed files with 96 additions and 53 deletions

View file

@ -70,8 +70,10 @@ includefile(memory/moving.yo)
subsect(Move-only classes (C++0x))
includefile(memory/moveonly)
COMMENT(
subsect(Defining move special member functions (C++0x, 4.6))
To do
END)
subsect(Moving: implications for class design (C++0x))
includefile(memory/moveimplications)

View file

@ -1,9 +1,10 @@
#include <string>
using namespace std;
int main()
{
//CODE
using std::string;
char buffer[3 * sizeof(string)];
string *sp = new(buffer) string [3];

View file

@ -1,5 +1,7 @@
#include <string>
#include <cstring>
#include <iostream>
using namespace std;
class Strings
{
@ -8,28 +10,43 @@ class Strings
size_t d_capacity;
public:
Strings(Strings &&tmp);
Strings();
~Strings();
void reserve(size_t request);
void append(std::string const &next);
private:
void reserve();
void destroy();
};
Strings::Strings()
:
d_memory(static_cast<string *>(operator new(sizeof(string)))),
d_size(0),
d_capacity(1)
{}
void Strings::reserve(size_t request)
{
if (request <= d_capacity)
return;
do
d_capacity <<= 1;
while (d_capacity < request);
reserve();
}
//RESERVE
void Strings::reserve()
{
using std::string;
string *newMemory =
static_cast<string *>(memcpy(
operator new(d_capacity),
d_memory,
d_size * sizeof(string)
));
delete d_memory;
string *newMemory = static_cast<string *>( // 1
operator new(d_capacity * sizeof(string)));
for (size_t idx = 0; idx != d_size; ++idx) // 2
new (newMemory + idx) string(d_memory[idx]);
destroy(); // 3
d_memory = newMemory;
}
//=
@ -45,12 +62,15 @@ void Strings::append(std::string const &next)
Strings::~Strings()
{
using std::string;
destroy();
}
//DESTROY
for (string *sp = d_memory + d_size; sp-- != d_memory; )
void Strings::destroy()
{
for (std::string *sp = d_memory + d_size; sp-- != d_memory; )
sp->~string();
operator delete(d_memory);
//=
}
//=

View file

@ -6,7 +6,7 @@ em(transfer) of the data pointed to by a temporary object to its destination.
Moving information is based on the concept of anonymous (temporary)
data. Temporary values are returned by functions like tt(operator-()) and
tt(opertor+(Type const &lhs, Type const &rhs)), and in general by functions
tt(operator+(Type const &lhs, Type const &rhs)), and in general by functions
returning their results `by value' instead of returning references or
pointers.

View file

@ -3,16 +3,18 @@ A remarkable form of operator tt(new) is called the emi(placement new)
header file must have been included.
With placement tt(new) operator tt(new) is provided with an existing block of
memory in which an object or value is initialized. The block of memory should
of course be large enough to contain the object, but apart from that no other
requirements exist. It is easy to determine how much memory is used by en
entity (object or variable) of type tt(Type): the
memory in which tt(new) initializes an object or value. The block of memory
should of course be large enough to contain the object, but apart from that
there are no other requirements. It is easy to determine how much memory is
used by en entity (object or variable) of type tt(Type): the
ti(sizeof) operator returns the number of bytes required by an tt(Type)
entity. Entities may of course dynamically allocate memory for their own use.
entity.
Entities may of course dynamically allocate memory for their own use.
Dynamically allocated memory, however, is not part of the entity's memory
`footprint' but it is always made available externally to the entity
itself. This is why tt(sizeof) returns the same value when applied to
different tt(string) objects returning different length and capacity values.
different tt(string) objects that return different length and capacity values.
The placement tt(new) operator uses the following syntax (using tt(Type) to
indicate the used data type):
@ -25,13 +27,14 @@ and tt(Type(arguments)) is any constructor of the class tt(Type).
The placement tt(new) operator is useful in situations where classes set
aside memory to be used later. This is used, e.g., by tt(std::string) to
change its capacity. Calling tt(string::reserve) may enlarge that capacity
without making memory beyond the string's length immediately available. But
the object itself may access its additional memory and so when information
is added to a tt(string) object it can draw memory from its capacity rather
than having to perform a reallocation for each single addition of information.
without making memory beyond the string's length immediately available to the
tt(string) object's users. But the object itself em(may) use its additional
memory. E.g, when information is added to a tt(string) object it can draw
memory from its capacity rather performing a reallocation for each single
character that is added to its contents.
Let's apply that philosophy to a class tt(Strings) storing tt(std::string)
objects. The class defines a tt(char *d_memory) accessing the memory holding
objects. The class defines a tt(string *d_memory) accessing the memory holding
its tt(d_size) string objects as well as tt(d_capacity - d_size) reserved
memory. Assuming that a default constructor initializes tt(d_capacity) to 1,
doubling tt(d_capacity) whenever an additional tt(string) must be stored, the
@ -43,48 +46,65 @@ by tt(reserve)) has been consumed;
it() properly deleting the installed strings and memory when a
tt(Strings) object ceases to exist.
)
To double the capacity new memory is allocated, old memory is copied into
the newly allocated memory, and the old memory is deleted. This is implemented
by the member tt(void Strings::reserve), assuming tt(d_capacity) has already
been given its proper value:
The private member tt(void Strings::reserve) is called when the current
capacity must be enlarged to tt(d_capacity). It operates as follows:
First new, raw, memory is allocated (line 1). This memory is in no way
initialized with strings. Then the available strings in the old memory are
copied into the newly allocated raw memory using placement new (line 2). Next,
the old memory is deleted (line 3).
verbinsert(RESERVE)(examples/strings.cc)
The member tt(append) adds another tt(string) object to a tt(Strings)
object. A (public) member tt(reserve(request)) ensures that the tt(String)
object's capacity is sufficient. Then the placement tt(new) operator is used
to install the next string into the raw memory's appropriate location:
object. A (public) member tt(reserve(request)) (enlarging tt(d_capacity) if
necessary and if enlarged calling tt(reserve())) ensures that the tt(String)
object's capacity is sufficient. Then placement tt(new) is used to install the
latest string into the raw memory's appropriate location:
verbinsert(APPEND)(examples/strings.cc)
At the end of the tt(String) object's lifetime all its dynamically
allocated memory must be returned. This is the responsibility of the
destructor, as explained in link(the next section)(DESTRUCTOR). The
destructor's full definition is postponed to that section, but its actions
when placement tt(new) is involved can be discussed here.
At the end of the tt(String) object's lifetime, and during enlarging
operations all currently used dynamically allocated memory must be
returned. This is made the responsibility of the member tt(destroy), which is
called by the class's destructor and by tt(reserve()). More about the
destructor itself in link(the next section)(DESTRUCTOR), but the
implementation of the support member tt(destroy) is discussed below.
With placement tt(new) an interesting situation is encountered. Objects,
possibly themselves allocating memory, are installed in memory that may or may
not have been allocated dynamically, but that is definitely not
completely filled with such objects. So a simple tt(delete[]) can't be used,
but a tt(delete) for each of the objects that em(are) available can't be used
either, since that would also delete the memory of the objects themselves,
which wasn't dynamically allocated.
not have been allocated dynamically, but that is usually not completely filled
with such objects. So a simple tt(delete[]) can't be used. On the other hand,
a tt(delete) for each of the objects that em(are) available can't be used
either, since those tt(delete) operations would also try to delete the memory
of the objects themselves, which wasn't dynamically allocated.
This peculiar situation is solved in a peculiar way, only
encountered in cases where the placement tt(new) operator has been used:
memory allocated by objects initialized using placement tt(new) is returned by
hi(destructor: explicit call)em(explicitly) calling the object's destructor.
The destructor is declared as a member having the class preceded by a tilde as
its name, not using any arguments. So, tt(std::string)'s destructor is named
tt(~string). The memory allocated by our class tt(Strings) is therefore
properly destroyed as follows (in the example assume that tt(using namespace
std) was specified):
This peculiar situation is solved in a peculiar way, only encountered in
cases where placement tt(new) is used: memory allocated by objects initialized
using placement tt(new) is returned by
hi(destructor: explicit call)
em(explicitly) calling the object's destructor. The destructor is
declared as a member having as its name the class name preceded by a tilde,
not using any arguments. So, tt(std::string)'s destructor is named
tt(~string). An object's destructor will only return memory allocated by the
object itself and it will em(not) destroy its object.
The memory allocated by the em(strings) stored in
our class tt(Strings) is therefore
properly destroyed by explicitly calling their destructors. Following this
tt(d_memory) is back to its initial status: it points to raw memory
again. This raw memory is then returned to the common pool by tt(operator
delete):
verbinsert(DESTROY)(examples/strings.cc)
So far, so good. All is well as long as we're using but one object. What
about allocating an array of objects? Initialization is performed as usual.
But as with tt(delete), tt(delete[]) cannot be called when the buffer was
allocated statically. Instead, when multiple objects were initialized using
the placement tt(new) operator in combination with a statically allocated
placement tt(new) in combination with a statically allocated
buffer all the objects' destructors must be called explicitly, as in the
following example:
verbinsert(CODE)(examples/placement2.cc)