Безопасность авто при std :: move от возвращенной ссылки на значение - PullRequest
1 голос
/ 18 июня 2019

Мне нужно некоторое заверение о том, когда, присваивая или список инициализируя именованную переменную auto, с

  • A a std::move () ed вернул ссылку на переменную
  • B возвращаемая ссылка на переменную

, где после выражения источник выходит из области видимости, A безопасен / B небезопасен. Пример кода:

#include <iostream>
#include <string>
#include <deque>
#include <utility>

int main() {
    std::deque<std::string> container {"foo"};
    auto elementB = container.front(); //B I assume this is unsafe
    auto elementA = std::move(container.front());//A I assume this is safe
    container.pop_front();

    std::cout << "A: " << elementA << " B: " << elementB  << "\n";
}

Насколько я понимаю, выражение B генерирует lvalue справа от присваивания, и поэтому тип elementB является ссылкой lvalue aka std::string&, поэтому он будет небезопасным.

Также вывод «A: foo B:» при выполнении кода предполагает это. (https://ideone.com/wKKbdK) Обновление: Извините, я забыл, что переместил его, поэтому я изменил порядок, и теперь вывод такой же, как исключение, извините.

Однако гораздо более неприятной вещью, в которой я не уверен, является выражение A: после std::move я предполагаю, что получил xvalue, который является как rvalue, так и glvalue, поэтому я не уверен, что такое стандартизированное поведение, если таковое имеется, для тип вычета elementA.

Поскольку из lvalues ​​я почти уверен, что его UB, а lvalues ​​являются glvalues, а xvalues ​​являются их частью, то тип будет elementA будет std::string&&, что было бы небезопасно, верно? (за исключением исключения для const && AFAIK)

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

Ответы [ 2 ]

4 голосов
/ 18 июня 2019

Безопасно ли использование стандартизированного поведения elementA?

Да.

... а какой будет его тип?

Это будет тип std::string. auto вывод типа работает как вывод типа шаблона, и это включает в себя удаление "ссылочной" ссылки. Тот факт, что std::move(container.front()) возвращает значение x, не сильно меняет здесь. Это «истекающее» значение, вы можете (а) переместить-построить новый объект (как вы это делаете в настоящее время) (б) связать его с const -качественной ссылкой или (в) связать его с rvalue-ссылкой , Здесь, (b) и (c) оба работают, но не имеют особого смысла, поскольку они скрывают тот факт, что ничего не удалено (спасибо @ M.M за исправление меня здесь). Пример:

auto elementA = std::move(container.front());
// Error, can't bind to non-const reference:
// auto& doesntWork = std::move(container.front());
auto&& thisWorks = std::move(container.front());
const auto& thisWorksToo = std::move(container.front());

Обратите внимание, что, как указал @ M.M в комментариях, последние две ссылки будут свисать при обнаружении container.pop_front();.

Также обратите внимание, что вычитание elementB как std::string не помогает тому факту, что вы разыменовываете перемещенный объект (возврат на container.front()), чего вам следует избегать.

2 голосов
/ 18 июня 2019

Ваш код в порядке, потому что оба значения elementA и elementB будут выводиться как string, а не как ссылка (string& или string&&).

Учитывая auto elementA, ссылочная часть инициализатора будет игнорироваться. Если вы хотите, чтобы elementA или elementB были ссылками, вы должны указать явно, например, auto&& elementA или auto& elementB.

Возможно, вы путаете категорию значений с типами; они независимы. Категория значений инициализатора, т. Е. Lvalue или rvalue, не повлияет на вычет типа . В вашем коде значение auto elementA, elementA будет иметь тип std::string, значение auto& elementA, его тип будет std::string&, значение auto&& elementA, его тип будет std::string&&.

...