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

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

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

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

1 ответ

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

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

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

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

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

  • Оптимизация возвращаемого значения. Вопрос помечен C++11, поэтому у вас нет гарантированного разрешения копирования для инициализации с использованием значений, полученных в C++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++ ("Предположим, что операции перемещения отсутствуют, не дешевы и не используются").

Другие вопросы по тегам