Могу ли я использовать объект распределителя для освобождения памяти, выделенной другим распределителем? - PullRequest
5 голосов
/ 21 января 2020

Насколько я знаю, std::allocator вводится библиотекой для выделения неинициализированных неструктурированных блоков памяти. Итак:

std::allocator<int> a;
auto ptr = a.allocate(100);
auto e = ptr;
while (e != ptr + 5)
    a.construct(e++, 0);

for (auto tmp = ptr; tmp != e; )
    std::cout << *tmp++ << ", ";
std::cout << std::endl;

std::allocator<int> a2;
std::allocator<int> a3 = a;

for (auto tmp = ptr; tmp != e; )
    a.destroy(tmp++);

//for (auto tmp = ptr; tmp != e; )
//  a2.destroy(tmp++); // is it UB using a2 here to destroy elements?

//for (auto tmp = ptr; tmp != e; )
//  a3.destroy(tmp++); // is it UB also?

a.deallocate(ptr, 100); // ok
//a2.deallocate(ptr, 100); // UB or OK?
//a3.deallocate(ptr, 100); // UB or ok?
  • В чем я не уверен, так это в том, использует ли другой распределитель a2, a3 объекты (один из них) для освобождения (освобождения) памяти, выделенной a является неопределенным поведением?

  • Если все в порядке, почему классы, подобные std::vector, имеют объект-распределитель, а не просто создают временный объект для выделения / освобождения c памяти?

Пожалуйста, уточните вопросы выше.

Ответы [ 2 ]

7 голосов
/ 21 января 2020

Мое чтение стандарта говорит о том, что это неопределенное поведение, если только распределители не сравняются. Таблица 34: Требования к Cpp17Allocator [tab: cpp17.allocator] указывает для

a.deallocate(p,n)

, что

Требуется: p должно быть значением, возвращаемым предыдущим вызовом allocate, который не был аннулирован промежуточным вызовом deallocate. n должен соответствовать значению, переданному allocate для получения этой памяти.

Где a - lvalue типа распределителя. Поскольку allocate является функцией-членом, я интерпретирую использование allocate в цитируемом тексте для обозначения a.allocate, что означает, что вам нужен тот же или равный объект для освобождения того, что было выделено.


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

6 голосов
/ 21 января 2020

Вы можете прочитать о спецификации для распределителей здесь: https://en.cppreference.com/w/cpp/named_req/Allocator. Они немного изменились в разных стандартных версиях, но я напишу о представлениях C ++ 17 и 20.

Когда вы выделяете память с помощью распределителя a1, она может быть освобождена только другим распределителем a2 если a1 == a2. Цитата из стандарта :

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

std::allocator<T> обычно реализуется как пустой тип без состояния. std::allocator<T>::is_always_equal::value равно true, поэтому все std::allocator<T> объекты равны. Таким образом, оба освобождения четко определены.

Если это был распределитель с сохранением состояния, то после AllocatorT a3 = a1, a1 == a3 должно быть true, так что вы можете безопасно освободить его. AllocatorT a2; default создает его, а a2 == a1 это , вероятно, не true, поэтому вы не можете освободить с помощью a2.

В C ++ 17 вы также можете сделать AllocatorT a3 = std::move(a1);. Это означает, что вы больше не можете освобождать с a1 (если только a1 == a3 после хода), но только с a3. Это было изменено в C ++ 20, поэтому вы можете копировать только распределители.

В настоящее время большинство реализаций контейнеров содержат объект, который наследуется от распределителя, поэтому, если он пуст, пустая базовая оптимизация используется для того, чтобы не занимать лишних байтов. Таким образом, даже если все объекты всегда одинаковы, не мешает использовать 0 дополнительных байтов для хранения распределителя (и если он является состоящим, в любом случае объект должен быть сохранен для освобождения). В C ++ 20 это, вероятно, будет реализовано с атрибутом [[no_unique_address]] для того же эффекта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...