2016-09-21 5 views
1

Я подозреваю, что я смутно знаю причину того, что я наблюдаю, но я хотел бы получить подтверждение или исправление и некоторое объяснение этому.возвращение rvalue? Или: почему конструктор копирования вызывается на `return <expression>`?

Я следующий код:

template <class T> 
class C 
{ 

public: 

    C() = default; 

    C(const C& rhs) : mem(rhs.mem) 
    { 
     std::cerr << "copy" << "\n"; 
    } 

    // does not call copy constructor twice 
    // friend C operator<<(C& src, unsigned shift) 
    // { 
    //  std::cerr << 1 << "\n"; 
    //  C tmp(src); 
    //  std::cerr << 2 << "\n"; 
    //  tmp <<= shift; 
    //  std::cerr << 5 << "\n"; 
    //  return tmp; 
    // } 

    // does call copy constructor twice 
    friend C operator<<(C& src, unsigned shift) 
    { 
     std::cerr << 1 << "\n"; 
     C tmp(src); 
     std::cerr << 2 << "\n"; 
     return (tmp <<= shift); 
    } 

    friend C& operator<<=(C& src, unsigned shift) 
    { 
     std::cerr << 3 << "\n"; 
     src.mem <<= shift; 
     std::cerr << 4 << "\n"; 
     return src; 
    } 

    T mem; 
}; 

int main() 
{ 
    C<int> c1; 

    c1 << 3; 
} 

У меня есть две версии C<T>::operator<<(C<T>, unsigned)

разница является один возвращает результат выражения:

return (tmp <<= shift);

другой возвращает переменную:

return tmp

До сих пор я думал, что эти две функции были бы идентичны семантически и один с return (tmp <<= shift); просто быть лучше стиль, как return a + 1 будет лучше, чем стиль int ret = a + 1; return ret. Это, по-видимому, не так и, вероятно, справедливо только для атомных типов данных. Выход версии с return (tmp <<= shift); выглядит следующим образом:

1 
copy 
2 
3 
4 
copy 

выход другого, как это:

1 
copy 
2 
3 
4 
5 

Является ли мое предположение верно, что Infact return (tmp <<= shift); не возвращает tmp после вызова <<= на ITM, но скорее создать новый объект в качестве копии tmp после того, как был вызван <<=?

+0

Вы должны действительно протестировать различные уровни оптимизации, если вы собираетесь тестировать, когда вызывается или когда вы создаете конструктор копирования. Вывод может быть совершенно другим в зависимости от компилятора, оптимизации компилятора и т. Д. – PaulMcKenzie

ответ

1

При возврате по значению должно быть инициализировано возвращаемое значение . Возвращаемое значение является объектом типа C.

В случае return (tmp <<= shift); это означает, что (tmp << shift) является инициатором для C. Так как это lvalue типа C, это копирование.

Существует специальное правило, заключающееся в том, что для оператора возврата return identifier; это может быть конструкция перемещения, хотя identifier является lvalue. Но это правило не распространяется на другие выражения (пока).

Другой вариант коды делает активировать специальное правило:

tmp <<= shift; 
return tmp; 

Здесь tmp может рассматриваться как RValue, что делает его движимым (а также это делает контекст элизии копии).

В ходе тестирования ваш компилятор реализовал копирование. Чтобы протестировать без копирования (если ваш компилятор поддерживает, чтобы пользователь мог его настроить), вам также нужно будет дать вашему C конструктору move. Предоставленный пользователем экземпляр-копий подавляет неявное генерирование конструктора move.

0

operator <<= не может возвращать ссылку на входном параметр (возможно, он будет возвращать ссылку на другой переменную static/global), поэтому компилятор не может легко решить, использовать оптимизацию возвращаемого значения, и необходимо вызвать копию конструктор.

В вашем вопрос: когда вы пишете return (tmp <<= shift), компилятор не знает, вернет ли ссылка (tmp <<= shift) ссылку на tmp, но если вы напишете return tmp, компилятор знает об этом и может его оптимизировать.