почему перебор unordered_map с unique_ptr вызывает пару с ключом const? - PullRequest
0 голосов
/ 23 апреля 2020

Я обнаружил ошибку компилятора, которую не могу понять, почему это происходит только для std :: map с std :: unique_ptr.

Предположим, у нас есть следующий неупорядоченный объект карты и код итерации для него:

std::unordered_map<uint32_t, std::shared_ptr<char>> map;

for (const std::pair<uint32_t, std::shared_ptr<char>>& item : map)
{
  // do something
}

Это хорошо скомпилировано, но если мы используем уникальный указатель вместо общего указателя, как показано ниже, то мы получим ошибку компилятора о типе пары итераций:

std::unordered_map<uint32_t, std::unique_ptr<char>> map;
for (const std::pair<uint32_t, std::unique_ptr<char>>& item : map)
{
  // do something 
}

error C2440: 'initializing': cannot convert from 'std::pair<const _Kty,_Ty>' to
 'const std::pair<uint32_t,std::unique_ptr<char,std::default_delete<_Ty>>>

После этой ошибки, мы можем просто добавить 'const' к типу ключа, и он скомпилируется.

for (const std::pair<const uint32_t, std::unique_ptr<char>>& item : map)
                      ^^^
                      ||| 

Почему эта ошибка компиляции происходит только для уникального указателя?

1 Ответ

0 голосов
/ 30 апреля 2020

Друг дал мне ответ, и я делюсь им.

Ответ заключается в концепции неявного преобразования (копирования), которое разрешено делать компилятору.

Давайте посмотрим на следующее простой пример:

const char  x = 4;
const char& r = x;  // r is reference for x. Checking their addresses yields the same 
                    // address.

const int&  ir = c; // implicit creation of object + conversion(copy). 
                    // ir is different type of x, therefor compiler does implicit
                    //  conversion(copy): it creates behind the scene an object of int, 
                    // convert x into this temporary object. The temporary int object is 
                    // then bound to the reference ir. Checking addresses of ir and x 
                    // yields different addresses because ir is reference of the temporary 
                    // object, not to x

Так что даже если мы используем планирование ссылок для простого наведения на существующий объект, мы можем фактически получить конструкцию объекта + копию (если типы различаются и между ними есть преобразование).

То же самое происходит в l oop, который я дал в вопросе:

std::unordered_map<uint32_t, std::shared_ptr<char>> map;

for (const std::pair<uint32_t, std::shared_ptr<char>>& item : map)
{
  // do something
}

, тогда как реальный объект, удерживаемый картой, имеет тип

std::pair<const uint32_t, std::shared_ptr<char>>

l oop использует ссылку другого типа:

std::pair<uint32_t, std::shared_ptr<char>> 

и, следовательно, за кулисами для каждой итерации создается неявно новый временный объект и выполняется операция копирования для преобразования.

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

Вот почему использование 'auto' может спасти вас от таких ошибок,

std::unordered_map<uint32_t, std::shared_ptr<char>> map;

for (const auto& item : map)
{
  // do something
}

Я сам иногда предпочитаю играть и использовать явную форму вместо 'auto', чтобы столкнуться с такими проблемами и научиться :)

...