Как в C++ можно предсказать, будет ли вызываться семантика перемещения или копирования?

Учитывая широту, которую компилятор C++ имеет в создании экземпляров временных объектов и в вызове механизмов, таких как оптимизация возвращаемого значения и т. д., не всегда ясно, глядя на какой-либо код, будет ли вызываться семантика перемещения или копирования (или сколько).

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

Есть ли способ четко (и просто) предсказать, где и сколько копий и перемещений может произойти в некотором коде? В идеале, чтобы это сделать, не нужно быть экспертом по внутренним устройствам компилятора.


person Ziffusion    schedule 24.08.2018    source источник


Ответы (1)


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

Я бы тут возразил. Использование семантики перемещения при проектировании класса обработки ресурсов должно выполняться независимо от того, как и когда в клиентском коде происходит конструирование копирования или перемещения. Когда есть move-ctor/assignment, клиентский код может быть спроектирован таким образом, чтобы использовать существование этих специальных функций-членов.

Есть ли способ четко (и просто) предсказать, где и сколько копий и перемещений может произойти в некотором коде?

Немного сложно сказать, что здесь означает просто, но я понимаю это так:

  • Учитывая, что в классе нет оператора перемещения/оператора присваивания, вы всегда получите копию. Это тривиально, но важно помнить при работе, например, с классы в устаревшем коде, которые имеют определяемые пользователем деструкторы и/или копирование/назначение, потому что в этом случае компилятор не генерирует перемещение/назначение.

  • Оптимизация возвращаемого значения. Вопрос помечен как С++ 11, поэтому у вас нет гарантированного исключения копии для инициализации с prvalue, полученными в С++ 17. Однако справедливо предположить, что идентичный механизм уже реализован вашим компилятором. Следовательно,

    struct A {};
    
    A func() { return A{}; }
    

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

    В качестве исключения из этого правила возвращаемые значения функций, которые также являются параметрами функции, не подходят для оптимизации возвращаемых значений. Следовательно, переместите/перешлите их, чтобы предотвратить копирование в случае, если A можно переместить:

    A func(A& a) { return std::move(a); }
    

    Следовательно, объект, созданный возвращаемым значением func(A&), будет построен с помощью перемещения.

  • Функциональные параметры сами по себе не показывают, как они себя ведут, это зависит от типа и его специальных функций-членов. Дано

    void f1(A a1) { A a2{std::move(a1)}; };
    void f2(A& a1) { /* Same as above. */ };
    void f1(A&& a1) { /* Again, same. */ };
    

    экземпляры a2 создаются с помощью перемещения, если A имеет движущийся ctor, в противном случае это копирование.

Помимо приведенных выше примерных случаев, можно многое узнать, я не могу вдаваться в подробности, и это не соответствовало бы желаемой простоте ответа. Кроме того, сценарий отличается, когда вы не знаете, с какими типами имеете дело, например. в шаблонах функций или классов. В этом случае полезно прочитать о том, как справиться с связанной с этим неопределенностью в отношении того, выполняются ли копии или перемещения, см. пункт 29 в Eff. Современный C++ («Предположим, что операций перемещения нет, они недешевы и не используются»).

person lubgr    schedule 24.08.2018
comment
Спасибо за вдумчивый ответ. Но если направление состоит в том, чтобы предположить, что операции перемещения отсутствуют, недешевы и не используются, это равносильно утверждению, что вы не можете полагаться на семантику перемещения в своем проекте. Нет? - person Ziffusion; 24.08.2018
comment
Вы правы, контекст этой цитаты несколько отсутствует. Я постараюсь улучшить ссылку на этот пункт. - person lubgr; 24.08.2018
comment
Небольшой нюанс: оптимизация возвращаемого значения (Named). Вопрос помечен как C++11, поэтому у вас нет гарантированного удаления копии, созданного C++17. Можно предположить, что C++17 гарантирует эту оптимизацию во всех случаях, которые вы упомянули, хотя это только для неназванных временных (и соответствующий тип). Так что в основном только операнд оператора возврата. Остальное по-прежнему находится в компетенции компилятора. Я имею в виду этот комментарий как просто явное разъяснение. - person luk32; 24.08.2018
comment
@ luk32 Ты прав, спасибо за подсказку. Я постараюсь уточнить ссылку на C++17 copy elision. - person lubgr; 24.08.2018