How are variables related when using an allocator in C++?

I’m studying this piece of code and what I don’t understand is how p, q and r are related. We assign p to q and p to r, then display r, even though we do the increment on q.

Then this do ... while loop:

do {
    cout<< "here" << *r << endl;
} while (++r != q);

How does it work? What are r and q equal to?

This is the full code:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
    allocator<string> alloc;
    auto const p = alloc.allocate(5);
    auto q=p;
    alloc.construct(q++);
    alloc.construct(q++, 10, 'c');
    alloc.construct(q++, "hi");
    auto r=p;
    
    do{
        cout<< *r << endl;
    }while (++r != q);
    
    std::cout<<"done"<<endl;
    
    while (q != p){
        alloc.destroy(--q);
    }
    
    q=p; r=p;
    alloc.construct(q++, 10, 'a');
    alloc.construct(q++, "hi again");
    
    do{
        cout<< "here" << *r << endl;
    }while (++r != q);
    
    alloc.deallocate(p, 5);
}

The output is:

output:
cccccccccc
hi
done
hereaaaaaaaaaa
herehi again

Answer

p is a std::string * const, aka “constant pointer to mutable string”. q and r are initialised to copies of p, meaning they are std::string *, aka “mutable pointer to mutable string”.

p always points to the first element of the allocated array. q and r are modified to point to other elements of the array.

The first block

auto q=p;
alloc.construct(q++);
alloc.construct(q++, 10, 'c');
alloc.construct(q++, "hi");

constructs 3 std::string objects, at consecutive positions in the array, starting with the first, incrementing q each time. q ends up pointing one-past the last std::string, to the forth element of the array.

The next block

auto r=p;    
do{
    cout<< *r << endl;
}while (++r != q);

displays these strings. After this, both q and r point to the forth element of the array.

The next block

while (q != p){
    alloc.destroy(--q);
}

destroys each of those strings, in reverse order of construction. q ends up pointing to the first element, the same as p.

The first statement of the next block is a tautology, it sets q to the value it currently has, and then constructs 2 more strings. r is also reset to p.

q=p; r=p;
alloc.construct(q++, 10, 'a');
alloc.construct(q++, "hi again");

The next block displays those new strings, prepending “here”.

do{
    cout<< "here" << *r << endl;
}while (++r != q);

Finally the array is deallocated, without destroying the std::string objects. This might leak memory that the strings allocate, as their destructors are not run.

alloc.deallocate(p, 5);