11

Что означает T&& (двойной амперсанд) в C++11?

13

Я изучаю новые возможности C++11, и одна из них, которую я заметил, — это двойной амперсанд при объявлении переменных, например, T&& var.

Во-первых, как называется эта конструкция? Мне бы хотелось, чтобы Google позволял искать подобные символы.

Что именно это означает?

На первый взгляд, кажется, что это двойная ссылка (похожая на двойные указатели в C — T** var), но я затрудняюсь найти практический пример её использования.

1 ответ(ов)

1

Это обозначает ссылку на rvalue (временный объект). Ссылки на rvalue привязываются только к временным объектам, если не задано иное. Они используются для повышения эффективности работы с объектами в определённых условиях и предоставляют механизм, известный как идеальная передача (perfect forwarding), что значительно упрощает шаблонный код.

В C++03 вы не можете отличить копию немутируемого lvalue от rvalue.

std::string s;
std::string another(s);           // вызывает std::string(const std::string&);
std::string more(std::string(s)); // вызывает std::string(const std::string&);

В C++0X это уже не так.

std::string s;
std::string another(s);           // вызывает std::string(const std::string&);
std::string more(std::string(s)); // вызывает std::string(std::string&&);

Рассмотрим реализацию этих конструкторов. В первом случае строка должна выполнить копирование для поддержания семантики значений, что подразумевает выделение новой памяти в куче. Однако во втором случае мы заранее знаем, что объект, переданный в конструктор, вскоре будет уничтожен, и его не нужно оставлять неизменным. Мы можем эффективно просто поменять внутренние указатели и избежать копирования в данном сценарии, что значительно более эффективно. Семантика перемещения выгодна для любого класса, для которого дорого или невозможно копирование внутренних ресурсов. Возьмём, к примеру, std::unique_ptr — теперь, когда наш класс может различать временные и нетемпоральные объекты, мы можем корректно реализовать семантику перемещения, так что unique_ptr нельзя копировать, но его можно перемещать, а это значит, что std::unique_ptr можно законно хранить в стандартных контейнерах, сортировать и так далее, в то время как std::auto_ptr из C++03 не позволяет этого.

Теперь рассмотрим другое применение ссылок на rvalue — идеальную передачу. Рассмотрим вопрос привязывания ссылки к ссылке.

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // обычно выражается через шаблон

Не помню, что говорит C03 об этом, но в C0x результатирующий тип при работе с ссылками на rvalue имеет критическое значение. Ссылка на rvalue типа T, где T — это тип ссылки, становится ссылкой типа T.

(std::string&)&& ref // ref — это std::string&
(const std::string&)&& ref // ref — это const std::string&
(std::string&&)&& ref // ref — это std::string&&
(const std::string&&)&& ref // ref — это const std::string&&

Рассмотрим самую простую функцию-шаблон — min и max. В C03 вы должны вручную перегрузить для всех четырёх комбинаций const и не-const. В C0x достаточно одной перегрузки. В сочетании с вариативными шаблонами это позволяет реализовать идеальную передачу.

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // например, если вы передадите const std::string& в качестве первого аргумента,
    // тогда A станет const std::string&, и, следовательно, aref также станет
    // const std::string&, полностью сохраняя информацию о типе.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

Я оставил без внимания вывод типа возвращаемого значения, потому что не припоминаю, как это делается наизусть, но этот min может принимать любую комбинацию lvalues, rvalues и const lvalues.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь