reallocate
MemberUsing this information, we can now write our reallocate
member. We’ll start by calling allocate
to allocate new space. We’ll double the capacity of the StrVec
each time we reallocate. If the StrVec
is empty, we allocate room for one element:
void StrVec::reallocate()
{
// we'll allocate space for twice as many elements as the current size
auto newcapacity = size() ? 2 * size() : 1;
// allocate new memory
auto newdata = alloc.allocate(newcapacity);
// move the data from the old memory to the new
auto dest = newdata; // points to the next free position in the new array
auto elem = elements; // points to the next element in the old array
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free(); // free the old space once we've moved the elements
// update our data structure to point to the new elements
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
The for
loop iterates through the existing elements and construct
s a corresponding element in the new space. We use dest
to point to the memory in which to construct the new string
, and use elem
to point to an element in the original array. We use postfix increment to move the dest
(and elem
) pointers one element at a time through these two arrays.
The second argument in the call to construct
(i.e., the one that determines which constructor to use (§ 12.2.2, p. 482)) is the value returned by move
. Calling move
returns a result that causes construct
to use the string
move constructor. Because we’re using the move constructor, the memory managed by those string
s will not be copied. Instead, each string
we construct will take over ownership of the memory from the string
to which elem
points.
After moving the elements, we call free
to destroy the old elements and free the memory that this StrVec
was using before the call to reallocate
. The string
s themselves no longer manage the memory to which they had pointed; responsibility for their data has been moved to the elements in the new StrVec
memory. We don’t know what value the string
s in the old StrVec
memory have, but we are guaranteed that it is safe to run the string
destructor on these objects.
What remains is to update the pointers to address the newly allocated and initialized array. The first_free
and cap
pointers are set to denote one past the last constructed element and one past the end of the allocated space, respectively.
Exercise 13.39: Write your own version of StrVec
, including versions of reserve
, capacity
(§ 9.4, p. 356), and resize
(§ 9.3.5, p. 352).
Exercise 13.40: Add a constructor that takes an initializer_list<string>
to your StrVec
class.
Exercise 13.41: Why did we use postfix increment in the call to construct
inside push_back
? What would happen if it used the prefix increment?
Exercise 13.42: Test your StrVec
class by using it in place of the vector<string>
in your TextQuery
and QueryResult
classes (§ 12.3, p. 484).
Exercise 13.43: Rewrite the free
member to use for_each
and a lambda (§ 10.3.2, p. 388) in place of the for
loop to destroy
the elements. Which implementation do you prefer, and why?
Exercise 13.44: Write a class named String
that is a simplified version of the library string
class. Your class should have at least a default constructor and a constructor that takes a pointer to a C-style string. Use an allocator
to allocate memory that your String
class uses.