2015-11-23 2 views
12

УчитываяКопия элизия для передачи по значению аргументов

struct Range{ 
    Range(double from, double to) : from(from), to(to) {} 
    double from; 
    double to; 
}; 

struct Box{ 
    Box(Range x, Range y) : x(x), y(y) {} 
    Range x; 
    Range y; 
}; 

Предположим, мы запускаем Box box(Range(0.0,1.0),Range(0.0,2.0)).

Может ли современный компилятор с оптимизацией разрешить копирование Range объектов во время этой конструкции? (т. е. построить объекты Range внутри box для начала?)

ответ

18

Есть фактически две копии выполняются на каждом Range объект передается в конструктор. Первое происходит при копировании временного объекта Range в параметр функции. Это можно отбросить в соответствии с рекомендацией, приведенной в ответе 101010. Есть specific circumstances, в котором может выполняться копирование.

Вторая копия происходит при копировании параметра функции в член (как указано в списке инициализации конструктора). Это невозможно устранить, и поэтому вы все равно видите, что для каждого параметра в ответе YSC делается одна копия.

Когда конструктор копирования имеет побочные эффекты (например, отпечатки в ответе YSC), копирование elision все равно может выполняться для первой копии, но вторая копия должна оставаться.

Однако компилятор всегда может вносить изменения, если они не изменяют наблюдаемое поведение программы (это известно как правило «как-если»). Это означает, что если конструктор копирования не имеет побочных эффектов и удаление вызова конструктора не изменит результат, компилятор может удалить даже вторую копию.

Вы можете увидеть это, проанализировав сгенерированную сборку. В this example, компилятор оптимизирует из не только копии, но даже строительство самого Box объекта:

Box box(Range(a,b),Range(c,d)); 
std::cout << box.x.from; 

Формирует идентичную сборку как:

std::cout << a; 
+1

Возможно, это могло бы быть улучшено, если бы вы более четко указали, что здесь есть две вещи: (1) Копировать Elision, которая позволяет копировать копии независимо от того, как реализован конструктор/деструктор копирования, является специальным правилом C++ и (2) Правило If, которое позволяет исключить любой код, который не изменяет поведение, является общим правилом оптимизатора.Другие ответы не удались (2), потому что они начали использовать вызовы конструктора/деструктора копирования, чтобы засвидетельствовать копии, которые, в свою очередь, препятствовали правилу «Как было» и, таким образом, оптимизаторы сохраняли поведение копии. –

+0

@ MatthieuM. Я редактировал, чтобы надеяться сделать различие более ясным. – interjay

+0

Это кажется более ясным, спасибо :) –

4

Должно быть, но I fail to make it work (live example). Компилятор может обнаружить побочный эффект конструкторов и решить не идти с копией.

#include <iostream> 

struct Range{ 
    Range(double from, double to) : from(from), to(to) { std::cout << "Range(double,double)" << std::endl; } 
    Range(const Range& other) : from(other.from), to(other.to) { std::cout << "Range(const Range&)" << std::endl; } 
    double from; 
    double to; 
}; 

struct Box{ 
    Box(Range x, Range y) : x(x), y(y) { std::cout << "Box(Range,Range)" << std::endl; } 
    Box(const Box& other) : x(other.x), y(other.y) { std::cout << "Box(const Box&)" << std::endl; } 
    Range x; 
    Range y; 
}; 


int main(int argc, char** argv) 
{ 
    (void) argv; 
    const Box box(Range(argc, 1.0), Range(0.0, 2.0)); 
    std::cout << box.x.from << std::endl; 
    return 0; 
} 

Compile & пробег:

clang++ -std=c++14 -O3 -Wall -Wextra -pedantic -Werror -pthread main.cpp && ./a.out 

Выход:

Range(double,double) 
Range(double,double) 
Range(const Range&) 
Range(const Range&) 
Box(Range,Range) 
1 
+2

Но вы не печать из конструктора копии. – Museful

+0

К сожалению. Я отредактировал свой ответ. – YSC

+1

Это делает игнорировать две копии конструкторы собираются в конструктор по значению, он просто не игнорировать копию из параметров в 'x' и' y' членов. Общим шаблоном является принятие по значению, затем 'std :: move' в члены данных. – TartanLlama

1

Да, может, в частности, этот вид контекста копия Elision подпадает под критерий копирования Пропуска, указанного в 12,8/p31.3 Копирование и перемещение объектов класса [класс.copy] стандарта:

(31,3) - когда объект временного класса, который не был связан с ссылкой (12.2) будут скопированы/перемещены в объект класса с тем же типом (игнорируя CV-квалификацию), копия/операция перемещения может быть опущена , создавая временный объект непосредственно в цель пропущенной копии/перемещения.

Любой спускаемый компилятор применяет копирование в определенном контексте. Однако в примере OP присутствуют две копии.

  1. Временные объекты, переданные в конструкторе (это может быть указано в стандарте, как указано выше).
  2. Копии в списке инициализаторов конструктора Box (это невозможно исключить).

Вы можете увидеть это в этом demo, где создатель копии вызывается только 2 раза.

Имейте в виду, что, поскольку стандарт разрешает в конкретной ситуации оптимизацию выравнивания контекста, это не означает, что поставщик компилятора обязан это сделать. Копирование elision - единственная разрешенная форма оптимизации, которая может изменять наблюдаемые побочные эффекты. Следовательно, из-за того, что некоторые компиляторы не выполняют копирование в любой ситуации, где это разрешено (например, в режиме отладки), программы , которые полагаются на побочные эффекты конструкторов и деструкторов копирования/перемещения, не являются переносимыми.

+3

Я не могу заставить его случиться. Примером может стать длинный путь, чтобы убедить меня. – Museful

+0

Понижающие голоса были бы настолько любезными, чтобы объяснить причину их проголосовавшего? Если есть действительно ...? – 101010

+0

Я не спустил вниз, но, вероятно, из-за [этого вопроса] из-за [этого вопроса] (http://stackoverflow.com/questions/33873047/will-any-compiler-actually-ever-elide-thesecopcopies), который ссылается на ваш ответ. Я полагаю, они отказались, потому что ваш ответ был частично неправильным до того, как вы отредактировали (вопрос спрашивает, можно ли полностью копировать копии, на что отвечать должно быть нет). – interjay

1

Тот факт, что он может, не означает, что это, безусловно, будет. Посмотрите на это в Demo, очевидно, что вы создаете две копии.Подсказка, выход содержит в два раза:

копия, сделанная

копия, сделанная

+0

Я не думаю, что это честный тест. Компилятор ** должен ** следовать правилу as-if и испускать код, который вызывает 'cout', который вы вставляете в код. Таким образом, вы ввели условие, препятствующее копированию копий. –

+1

@EvanTeran не соответствует действительности. copy elision - это тот случай, когда компилятор делает ** не **, должен поддерживать побочные эффекты, которые были бы обязательными во всем мире в соответствии с правилом _as-if_ и позволяли их отбрасывать. –

+0

@underscore_d, интересно, спасибо за исправление. –

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