Почему вектор unique_ptr является предпочтительным способом хранения указателей? - PullRequest
0 голосов
/ 16 июня 2019

То, что я прочитал, говорит, что общий подход к созданию вектора указателя, которому принадлежат указатели, например, MyObject для простых целей, это vector<unique_pointer<MyObject>>.

Но каждый раз, когда мы получаем доступ к элементу, мы вызываем unique_ptr::get(). Также есть небольшие накладные расходы.

Почему вектор указателя с "пользовательским удалителем", если такая вещь существует (я не использовал распределители), более стандартен? То есть умный вектор вместо вектора умного указателя. Это устранит небольшие накладные расходы при использовании unique_ptr::get().

Что-то вроде vector<MyObject*, delete_on_destroy_allocator<MyObject>> или unique_vector<MyObject>.

Вектор будет использовать поведение «удалить указатель при уничтожении» вместо дублирования этого поведения в каждом unique_ptr, есть ли причина, или просто издержки пренебрежимо малы?

Ответы [ 2 ]

1 голос
/ 16 июня 2019

Почему вектор указателя не имеет "пользовательского удалителя", если такая вещь существует

Потому что такая вещь не существует и не может существовать.

Распределитель, предоставленный контейнеру, существует для выделения памяти для контейнера и (необязательно) создает / уничтожает объекты в этом контейнере.A vector<T*> - контейнер указателей;следовательно, распределитель выделяет память для указателя и (необязательно) создает / уничтожает указатели.Он не несет ответственности за содержимое указателя: объект, на который он указывает.Это домен пользователя для предоставления и управления.

Если распределитель берет на себя ответственность за уничтожение объекта, на который указывает объект, то он должен также логически нести ответственность за создание объекта, на который указываетда?В конце концов, если этого не произойдет, и мы скопируем такой vector<T*, owning_allocator>, каждая копия будет ожидать уничтожения объектов, на которые указывают.Но так как они указывают на одни и те же объекты (копирование vector<T> копирует T s), вы получаете двойное уничтожение.

Поэтому, если owning_allocator::destruct собирается удалить память, owning_allocator::construct должен также создать объект, на который указывают.

Итак ... что это делает:

vector<T*, owning_allocator> vec;
vec.push_back(new T());

Видите проблему?allocator::construct не может решить, когда создавать T, а когда нет.Он не знает, вызывается ли он из-за операции vector копирования или потому, что push_back вызывается с созданным пользователем T*.Все, что он знает, это то, что он вызывается со значением T* (технически это ссылка на T*, но это не имеет значения, поскольку он будет вызываться с такой ссылкой в ​​обоих случаях).

Поэтомулибо он 1) размещает новый объект (инициализируется с помощью копии из указанного ему указателя), либо 2) копирует значение указателя.А поскольку он не может определить, какая ситуация находится в игре, он всегда должен выбирать один и тот же вариант.Если он # 1, то приведенный выше код является утечкой памяти, потому что вектор не сохранил new T(), и никто больше не удалил его.Если он # 2, то вы не можете скопировать такой вектор (и история о внутреннем перераспределении векторов одинаково туманная).

То, что вы хотите, невозможно.

A vector<T> является контейнером T s, каким бы T ни был.T трактуется как есть;любое значение , означающее этого значения, остается за пользователем.И семантика владения является частью этого значения.

T* не имеет семантики владения, поэтому vector<T*> также не имеет семантики владения.unique_ptr<T> имеет семантику владения, поэтому vector<unique_ptr<T>> также имеет семантику владения.

Вот почему Boost имеет ptr_vector<T>, то есть явно класс в векторном стиле, который специально содержит указатели наT s.Из-за этого у него слегка измененный интерфейс;если вы вручите ему T*, он знает, что принимает T*, и уничтожит его.Если вы передаете ему T, то он выделяет новый T и копирует / перемещает значение во вновь выделенное T.Это другой контейнер, с другим интерфейсом и другим поведением;следовательно, он заслуживает другого типа от vector<T*>.

1 голос
/ 16 июня 2019

Ни вектор из unique_ptr, ни вектор простых указателей не являются предпочтительным способом хранения данных.В вашем примере: std::vector<MyObject> обычно просто отлично, и если вы знаете размер во время компиляции, попробуйте std::array<int>.

Если вам абсолютно необходимы косвенные ссылки, вы также можете рассмотреть std::vector<std::reference_wrapper<MyObject>>.Читайте о справочных упаковщиках здесь .

Сказав это ... если вам:

  • Нужно хранить ваш вектор где-то еще, чем ваши фактические данные, или
  • Если MyObject s очень большие / дорогие для перемещения, или
  • Если строительство или разрушение MyObject s имеет реальные побочные эффекты, которых вы хотите избежать;

и, кроме того, вы хотите, чтобы ваш MyObject был освобожден, когда на него больше нет ссылок из вектора, - вектор уникальных указателей актуален.

Теперь указатели простопростой и простой тип данных, унаследованный от языка Си;он не имеет пользовательских средств удаления или чего-либо другого ... но - std::unique_ptr поддерживает поддержку пользовательских средств удаления .Кроме того, это может быть случай, когда у вас есть более сложные потребности в управлении ресурсами, для которых не имеет смысла, чтобы каждый элемент управлял своим собственным распределением и отменой распределения - в этом случае может быть уместен «умный» векторный класс.

Итак: разные структуры данных соответствуют разным сценариям.

...