2015-07-11 4 views
5

Если посмотреть на get, функция помощника для std::tuple, вы заметите следующие перегрузки:Почему получить помощник станд :: возвратного кортеж RValue ссылки вместо значения

template< std::size_t I, class... Types > 
constexpr std::tuple_element_t<I, tuple<Types...> >&& 
get(tuple<Types...>&& t); 

Другими словами, она возвращает ссылку rvalue, когда входной кортеж является самой ссылкой rvalue. Почему бы не вернуться по значению, вызвав move в тело функции? Мой аргумент следующий: возврат get будет либо привязан к ссылке, либо значению (это может быть связано ни с чем, я полагаю, но это не должно быть обычным прецедентом). Если он привязан к значению, то в любом случае произойдет конструкция перемещения. Таким образом, вы ничего не теряете, возвращаясь по стоимости. Если вы привязываетесь к ссылке, то возврат ссылки rvalue может быть небезопасным. Для того, чтобы показать пример:

struct Hello { 
    Hello() { 
    std::cerr << "Constructed at : " << this << std::endl; 
    } 

    ~Hello() { 
    std::cerr << "Destructed at : " << this << std::endl; 
    } 

    double m_double; 
}; 

struct foo { 
    Hello m_hello; 
    Hello && get() && { return std::move(m_hello); } 
}; 

int main() { 
    const Hello & x = foo().get(); 
    std::cerr << x.m_double; 
} 

При запуске эта программа печатает:

Constructed at : 0x7ffc0e12cdc0 
Destructed at : 0x7ffc0e12cdc0 
0 

Другими словами, х сразу зависшего ссылки. Если вы только что написали foo вот так:

struct foo { 
    Hello m_hello; 
    Hello get() && { return std::move(m_hello); } 
}; 

Эта проблема не возникла. Кроме того, если вы затем использовать Foo так:

Hello x(foo().get()); 

Это не похоже, есть какие-либо дополнительные накладные расходы, вы возвращаете по значению или ссылки RValue. Я протестировал такой код, и кажется, что он будет последовательно выполнять только одну конструкцию движения. Например. если добавить элемент:

Hello(Hello &&) { std::cerr << "Moved" << std::endl; } 

И я построить х, как указано выше, моя программа только печатает «Перемещенные» один раз, независимо от того, возвращать я по значению или ссылки RValue.

Есть ли веская причина, по которой мне не хватает, или это надзор?

Примечание: здесь есть хороший вопрос: Return value or rvalue reference?. Кажется, что возврат стоимости обычно предпочтительнее в этой ситуации, но тот факт, что он появляется в STL, заставляет меня любопытно, игнорирует ли STL это рассуждение, или если у них есть свои собственные собственные причины, которые могут быть не применимы в общем.

Редактировать: Кто-то предположил, что этот вопрос является дубликатом Is there any case where a return of a RValue Reference (&&) is useful?. Это не тот случай; этот ответ предполагает возвращение ссылкой rvalue как способ ускорить копирование данных. Как я подробно рассмотрю выше, копирование будет отменено, если вы вернетесь по ссылке value или rvalue, если вы сначала вызываете move.

+0

Это не удаленный дубликат. Мой ответ явно и подробно обсуждает, как возврат по значению и вызов std :: move заранее достигает такого же эффекта перехода от элемента данных. Мой вопрос связан с различиями в двух подходах. Вопрос, который я цитировал, более связан с моим вопросом, чем тот, который вы указали. Пожалуйста, внимательно прочитайте, прежде чем спешить, чтобы наклеить что-то дублирующее. –

+0

, если вы вернетесь по значению - вы создаете копию. если вы переместите его, вы переместите копию. если вы вернете ссылку на r-значение, вы можете либо переместить ее, то исходный объект будет перемещен или не будет иметь дело с семантикой перемещения, тогда он будет вести себя как обычная ссылка. Дело в том, что если вы вернете ссылку на r-значение, вы не будете принудительно копировать. –

+0

Зачем нужна конструкция конструкции? – Columbo

ответ

4

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

Рассмотрим более простой пример, который не имеет никакого && нигде:

const int &x = vector<int>(1) .front(); 

.front() возвращает & -reference к первому элементу нового построенного вектора. Вектор сразу же уничтожается, и вы остаетесь с болтающейся ссылкой.

Урок, который следует изучать, заключается в том, что использование ссылки const не продлевает срок службы. Он продлевает срок службы без ссылок. Если правая сторона = является ссылкой, то вы должны взять на себя ответственность за время жизни самостоятельно.

Это всегда имело место, поэтому для tuple::get это не имело значения, чтобы сделать что-то другое. tuple::get разрешено возвращать ссылку, точно так же, как vector::front.

Вы говорите о движении и копировании конструкторов и о скорости. Самое быстрое решение - не использовать никаких конструкторов. Представьте себе функцию конкатенации двух векторов:

vector<int> concat(const vector<int> &l_, const vector<int> &r) { 
    vector<int> l(l_); 
    l.insert(l.end(), r.cbegin(), r.cend()); 
    return l; 
} 

Это позволило бы оптимизированную дополнительные перегрузки:

vector<int>&& concat(vector<int>&& l, const vector<int> &r) { 
    l.insert(l.end(), r.cbegin(), r.cend()); 
    return l; 
} 

Эта оптимизация сохраняет число конструкций до минимального

vector<int> a{1,2,3}; 
    vector<int> b{3,4,5}; 
    vector<int> c = concat(
    concat(
     concat(
      concat(vector<int>(), a) 
     , b) 
     , a 
    , b); 

В последней строке , с четырьмя звонками до concat, будет иметь только две конструкции: Начальное значение (vector<int>()) и move-construct в c. У вас может быть 100 вложенных вызовов на concat там, без каких-либо дополнительных конструкций.

Итак, возвращение && может быть быстрее. Потому что, да, ходы быстрее, чем копии, но еще быстрее, если вы можете избежать обоих.

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

И это не представляет никаких новых рисков в отношении срока службы. Проблема «vector<int>().front()» не является новой.

+2

Мне нужно потратить немного больше времени на ответьте, чтобы полностью поглотить его. Но обратите внимание, что по иронии судьбы ваш пример с vector.front не был бы великодушным, если бы была перегрузка T front() &&. –

+2

Я думал об этом. Да, комитет должен помещать '&' и '&&' во всем стандарте, как ваш пример в вашем комментарии. Но я предполагаю, что это нарушит совместимость. Кроме того, возможно, все методы должны быть '&' по умолчанию, но это тоже было бы довольно резким изменением языка. –

+0

Чтобы обобщить, если функция возвращает один из аргументов или подобъект аргумента, и этот аргумент равен '&&', тогда я не вижу причины, чтобы всегда не возвращать '&&'. Неужели я зашел слишком далеко? –

Смежные вопросы