Распределитель С++ и пул памяти

Я что-то смущен. Скажем, у меня есть произвольный дистрибутор С++ - скажем, что-то вроде этого:

template<class T>
struct my_allocator
{
    template<class Other>
    struct rebind { typedef my_allocator<Other> other; };

    // [other members here]
};

Теперь рассмотрим следующий код (прочитайте комментарии):

typedef my_allocator<int> Alloc;
Alloc alloc = get_my_allocator();  // assume this works properly

long *const p = Alloc::rebind<long>::other(alloc).allocate(1, NULL);
// Notice that the rebound allocator for 'long' is now destroyed

// Can a NEW rebound allocator for 'long' deallocate the memory from the old one?
Alloc::rebind<long>::other(alloc).deallocate(p, 1);
// i.e., does the 'int' allocator 'alloc' keep alive the 'long' memory pool too?

В какой момент может быть освобожден резервный пул хранения?

Или, другими словами: какой распределитель разделяет право собственности на какой пул памяти?

Я всегда предполагал, что без выделения второй мысли, что распределители типа одинакового типа разделяли совместное владение собственными пулами памяти, но теперь мне пришло в голову, что они также могут делиться собственностью пул памяти за всеми распределителями отскока, даже если они управляют совершенно разными типами.

Должны ли распределители типов отскока "поддерживать" пулы памяти друг друга до тех пор, пока все они не будут уничтожены?

Если для С++ 03 и С++ 11 ответ отличается, объясните, как и разница между ними.

Ответы

Ответ 1

Должны ли распределители типов отскока "поддерживать" пулы памяти друг друга до тех пор, пока все они не будут уничтожены?

Короткий ответ - да, хотя, по общему признанию, с оговорками. Долгий ответ следует...


С++ 11 (и С++ 14) говорит [allocator.requirements], что my_allocator<long>, построенный из my_allocator<int>, должен иметь возможность удалять память, выделенную из my_allocator<int>. Это выражается в таблице требований Allocator как:

X a(b);

с пост-условием:

Y(a) == b and a == X(b)

Как вы знаете, оператор == используется здесь для обозначения: два равных распределителя, которые могут быть освобождены друг от друга выделенными указателями. Кроме того, табличные документы, что b является объектом типа Y, где X и Y являются распределителями, связанными с rebind, как показано выше.

Теперь это одно не отвечает на ваш точный вопрос, так как в вашем вопросе my_allocator<int> никогда ничего не выделяет. Однако в следующей строке в этой же таблице говорится следующее о операторе распределителя ==:

a1 == a2

Пост-условие:

возвращает true только в том случае, если память, выделенная из каждого, может быть освобождена через другую. оператор == должен быть рефлексивным, симметричным и переходный и не должен выходить через исключение.

(акцент мой)

Transitive означает, что если a1 == a2 и a2 == a3, то подразумевается, что a1 == a3.

Эта деталь наделяет ваш вопрос. Первый временный my_allocator<long> скопирован из alloc и, таким образом, равен alloc.

Второй временный my_allocator<long> также скопирован из alloc и, следовательно, также равен alloc. Кроме того, из-за транзитивного свойства два временных my_allocator<long> также должны быть равны друг другу.

Это не означает, что все они должны использовать один и тот же пул памяти. Но это означает, что все три из этих распределителей должны иметь возможность как-то освободить друг друга выделенными указателями. То есть ваш пример должен работать.

С++ 03 не имеет "переходного" требования. При этом добавление "переходных" к формулировке С++ 11 считалось просто "очисткой" намерения С++ 03, а не новым требованием. Поэтому юристы-юристы могут утверждать, требуется или нет транзитивность в С++ 98/03, но с практической точки зрения код лучше предположить, что это было, потому что это было целью.

В самом деле, С++ 98/03 также включал эту "ласку" (больше не в С++ 11/14):

Все экземпляры данного типа распределителя должны быть взаимозаменяемы и всегда сравниваются друг с другом.

т.е. Контейнеры С++ 98/03 допускали, что все экземпляры (действительно даже экземпляры отскока) всегда равны. Официальная поддержка "stateful" распределителей действительно не началась до С++ 11.