Manage Your STL Container’s Storage with Self-swapping Idioms

enerally, STL containers handle their storage automatically and efficiently. Yet, there are cases when you need to regulate a container’s storage manually?for example when you want to trim or empty it. This article shares two simple and effective techniques to accomplish these tasks.


How do you trim a container whose capacity is larger than necessary? How do you force a container to destroy its elements and set its capacity to 0?


Use the self-swapping idioms.

Presenting the Problem
Containers that remain alive for long periods can degrade performance since they are quick to grow but reluctant to shrink. Take, for instance, a mail server that stores incoming messages in a vector before dispatching them to the clients. At peak time, the vector is filled with thousands of messages, causing its capacity to grow accordingly. After the short peak, the vector contains only a few messages at any given time. However, its capacity remains high.

Author’s Note: The member function capacity() reports the number of elements that the container can hold without requiring reallocation. size() returns the number of elements currently stored in the container. capacity() – size() is therefore the number of available “free slots” that can be filled with additional elements without reallocation. Notice that these member function count elements, not bytes.

In an environment where peak times are very frequent, retaining a high capacity value makes sense. However, if peak times are relatively rare or short, you may need to “trim” the container, ensuring that its capacity matches its size. Unfortunately, STL containers don’t define a trim() member function. However, it’s relatively easy to achieve this goal with a few steps.

Trimming a Container
When you copy a container, the target’s capacity is the same as its size. For example, if you have a vector of integers whose capacity and size are 100 and 1, respectively, a copy of this vector will have the same size as the source, but its capacity will be identical to its size. The following program demonstrates this behavior:

#include #include using namespace std;int main(){ vector  vi; vi.reserve(100); //enforce a capacity of 100 vi.push_back(5); //size is 1 cout<<"capacity of vi is: "< vi2=vi; cout<<"capacity of vi2 is: "<

The output is:

capacity of vi is: 100size     of vi is: 1capacity of vi2 is: 1size     of vi2 is: 1

This behavior is more than a hint: to trim a container c1, simply copy it to another container c2 and then copy c2 to c1:

vi2=vi; //vi2 is a trimmed copy of vivi=vi2; //trims vi

Swapping the Right Way
Manual copying is tedious and error-prone. Ideally, you want to perform the trimming operation in a single statement, while avoiding the creation of a dummy container. STL containers define a specialized version of the swap() algorithm as a member function. As a rule, when you have a choice between a generic algorithm and the container's equivalent member function, prefer the latter. Instead of calling

#include std::swap(vi, vi2); //works, but less efficient 

use:

vi.swap(vi2); //recommended

The member algorithm is optimized for its container. As such, it's usually more efficient than the generic version.

A final refinement to the design also eliminates the dummy container. Simply swap the container with itself:

vector(vi).swap(vi);

Let's see how it works. The expression

vector (vi)

creates a temporary trimmed copy of vi. Then, the swap(vi) call is evaluated, assigning the trimmed temporary to vi, and vice versa. Finally, the temporary object is destroyed.

Here's a modified version of the previous program, now using the swap idiom:

int main(){ vector  vi; vi.reserve(100);  vi.push_back(5);  cout<<"capacity before trimming: "<(vi).swap(vi);//trim vi cout<<"capacity after trimming : "<

As expected, the output is:

capacity before trimming: 100size before trimming: 1capacity after trimming: 1size after trimming: 1

Emptying a Container
Emptying a container raises a similar problem. The clear() member function destroys the currently stored elements and resets the container's size to 0. However, it doesn't change the container's capacity. Consider:

vi.reserve(100); //enforce a capacity of 100vi.push_back(5); //size is 1vi.clear(); //size is 0, but the capacity is unchangedcout<<"capacity: "<

To force a container to release both its elements and capacity, use a slightly different version of the swap idiom. Instead of swapping a container with a temporary copy of itself, swap it with an empty temporary:

vector().swap(vi);//sets vi's size and capacity to 0
Share the Post:
Share on facebook
Share on twitter
Share on linkedin

More From DevX

DevX is the leading provider of technical information, tools, and services for professionals developing corporate applications.

Subscribe

Get exclusive access to the best technical information, tools, and services sent straight to your inbox.  

©2022 Copyright DevX - All Rights Reserved. Registration or use of this site constitutes acceptance of our Terms of Service and Privacy Policy.