Удаленная функция в std :: pair при использовании unique_ptr внутри карты - PullRequest
0 голосов
/ 28 ноября 2018

У меня есть фрагмент кода C ++, для которого я не уверен, является ли он правильным или нет.Рассмотрим следующий код.

#include <memory>
#include <vector>
#include <map>

using namespace std;

int main(int argc, char* argv[])
{
    vector<map<int, unique_ptr<int>>> v;
    v.resize(5);

    return EXIT_SUCCESS;
}

GCC компилирует этот код без проблем.Компилятор Intel (версия 19), однако, останавливается с ошибкой:

/usr/local/ [...] /include/c++/7.3.0/ext/new_allocator.h(136): error: function "std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2> &) [with _T1=const int, _T2=std::unique_ptr<int, std::default_delete<int>>]" (declared at line 292 of "/usr/local/ [...] /include/c++/7.3.0/bits/stl_pair.h") cannot be referenced -- it is a deleted function
    { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
                            ^
      detected during:

[...]

instantiation of "void std::vector<_Tp, _Alloc>::resize(std::vector<_Tp, _Alloc>::size_type={std::size_t={unsigned long}}) [with _Tp=std::map<int, std::unique_ptr<int, std::default_delete<int>>, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, std::default_delete<int>>>>>, _Alloc=std::allocator<std::map<int, std::unique_ptr<int, std::default_delete<int>>, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, std::default_delete<int>>>>>>]"
                  at line 10 of "program.cpp"

Оба компилятора без проблем компилируют следующий код.

#include <memory>
#include <vector>
#include <map>

using namespace std;

int main(int argc, char* argv[])
{
    vector<unique_ptr<int>> v;
    v.resize(5);

    return EXIT_SUCCESS;
}

Первый код завершается ошибкой с Intelкомпилятор, потому что он пытается создать копию unique_ptr, который определяет только конструктор перемещения.Однако я не уверен, является ли первая программа допустимой программой на C ++.

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

1 Ответ

0 голосов
/ 29 ноября 2018

Проблема возникает из следующего пост-условия std::vector<T>::resize, [vector.capacity] :

Примечания: Если исключениеброшенный иначе как конструктором перемещения не- CopyInsertable T, никаких эффектов нет.

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

Изменяет ли копирование элементов оригинальное хранилище?Нет 1 .Изменяет ли перемещение элементы оригинальное хранилище?Да.Какая операция более эффективна?Перемещение.Может ли вектор всегда предпочитать перемещение копированию?Не всегда.

Если конструктор перемещения может выдать исключение, невозможно восстановить исходное содержимое старого хранилища, поскольку попытка переместить уже сдвинутые элементы обратно в старый блок может завершиться неудачей снова .В таком случае вектор будет использовать конструктор перемещения для перемещения своих элементов из старого хранилища в новое только , если этот конструктор перемещения гарантирует, что он не вызовет исключение (или конструктор перемещения является единственнымопция, когда конструктор копирования недоступен).Как функция обещает, что она не выдаст исключение?Один из них будет аннотирован с помощью спецификатора noexcept и протестирован с оператором noexcept.

Тестирование приведенного ниже кода с помощью icc:

std::map<int, std::unique_ptr<int>> m;
static_assert(noexcept(std::map<int, std::unique_ptr<int>>(std::move(m))), "!");

завершится неудачно при подтверждении.Это означает, что m является , а не nothrow- MoveConstructible .

Требует ли стандарт, чтобы он был noexcept? [map.overview] :

// [map.cons], construct/copy/destroy:
map(const map& x);
map(map&& x);

std::map равно Move- и CopyConstructible .Ни от того, ни от другого не требуется исключение.

Однако реализация может предоставить эту гарантию {{цитата нужна}} .Ваш код использует следующее определение:

map(map&&) = default;

Требуется ли неявно сгенерированный конструктор перемещения как noexcept? [кроме.spec] :

Наследующий конструктор ([class.inhctor]) и неявно объявленная специальная функция-член (Clause [special]) имеют исключение -specification .Если f является наследующим конструктором или неявно объявленным конструктором по умолчанию, конструктором копирования, конструктором перемещения , деструктором, оператором копирования или операцией назначения перемещения, его неявная спецификация исключений указывает идентификатор типа T тогда и только тогда, когда T разрешено спецификацией исключений функции, непосредственно вызванной неявным определением f;f разрешает все исключения, если любая функция, которую она непосредственно вызывает, разрешает все исключения, а f имеет спецификацию исключений noexcept(true) , если каждая функция, которую она непосредственно вызывает, не допускает исключений.

На данный момент трудно сказать, должен ли неявно сгенерированный icc конструктор перемещения быть noexcept или нет.В любом случае, std::map само по себе не требовалось быть nothrow- MoveConstructible , так что это больше проблема качества реализации (реализация библиотеки или реализация неявной генерации конструкторов), и icc избегает этого независимо от того,это фактическая ошибка или нет.

В конце концов, std::vector вернется к использованию более безопасной опции, которая является конструктором копирования для перемещения его элементов (карт уникальных указателей), но так как std::unique_ptr являетсяне CopyConstructible , сообщается об ошибке.

С другой стороны, конструктор перемещения std::unique_ptr равен , должен быть noexcept, [unique.ptr.single.ctor] :

unique_ptr(unique_ptr&& u) noexcept;

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


В более новой версии stl_map.h есть следующее пользовательское определение конструктора перемещения карты:

map(map&& __x)
  noexcept(is_nothrow_copy_constructible<_Compare>::value)
  : _M_t(std::move(__x._M_t)) { }

, которое явно делает noexcept зависимым только от того, копирует или нет бросок компаратора.


1 Технически, конструктор копирования, принимающий неконстантную ссылку на l-значение, может изменить исходный объект, например, std :: auto_ptr, но MoveInsertable требует, чтобы векторные элементы были конструируемыми из r-значений, которые не могут быть привязаны к неконстантным ссылкам l-значений.

...