Может ли компилятор удалить следующую копию?
Я все еще новичок-программист, я знаю, что преждевременная оптимизация плохая, но я также знаю, что копирование огромного материала вокруг тоже плохо.
Я прочитал о копировании elision, и это синонимы, но примеры в Википедии, например, заставляют мне показаться, что копирование может выполняться только в том случае, если возвращаемый объект возвращается, в то же время он полностью конструируется.
Как насчет объектов, таких как векторы, которые обычно имеют смысл при заполнении чем-то, когда они используются как возвращаемое значение.
В конце концов, пустой вектор можно просто создать вручную.
Итак, это также работает в таком случае?
плохой стиль для краткости:
vector<foo> bar(string baz)
{
vector<foo> out;
for (each letter in baz)
out.push_back(someTable[letter]);
return out;
}
int main()
{
vector<foo> oof = bar("Hello World");
}
У меня нет реальной проблемы с использованием bar (vector и out, string text), но вышеприведенный способ будет выглядеть намного лучше, эстетически и намеренно.
Ответы
Ответ 1
примеры в википедии, например, мне кажутся, что копирование может выполняться только в том случае, если возвращаемый объект возвращается в то же время, когда он полностью сконструирован.
Это вводит в заблуждение (читайте: неправильно). Проблема в том, что только один объект возвращается во всех кодах, т.е. Происходит только одна конструкция для потенциального объекта возврата.
Ваш код в порядке, любой современный компилятор может удалить копию.
С другой стороны, следующий код может создавать проблемы:
vector<int> foo() {
vector<int> a;
vector<int> b;
// … fill both.
bool c;
std::cin >> c;
if (c) return a; else return b;
}
Здесь компилятор должен полностью построить два разных объекта, и только позже решает, какой из них будет возвращен, поэтому он должен копировать один раз, потому что он не может напрямую построить возвращенный объект в целевой памяти.
Ответ 2
Нет ничего, что помешало бы компилятору ускорить копирование. Это определено в 12.8.15:
[...] Это разрешение операций копирования разрешено в следующих обстоятельства (которые могут быть объединены для устранения нескольких копий):
[...]
- когда объект временного класса, который имеет не были связаны с ссылкой (12.2) будет скопирован в объект класса с тот же самый cv-неквалифицированный тип, копия операция может быть опущена построение временного объекта прямо в цель пропущенная копия
Если это действительно зависит от компилятора и настроек, которые вы используете.
Ответ 3
Обе подразумеваемые копии vector
могут - и часто - устранены. Именованная оптимизация возвращаемого значения может исключить копию, указанную в операторе return return out;
, и разрешено также исключить из временного подразумеваемого в инициализации копирования oof
.
При обеих оптимизации в игре объект, построенный в vector<foo> out;
, является тем же объектом, что и oof
.
Легче проверить, какая из этих оптимизаций выполняется с помощью искусственного тестового примера, такого как.
struct CopyMe
{
CopyMe();
CopyMe(const CopyMe& x);
CopyMe& operator=(const CopyMe& x);
char data[1024]; // give it some bulk
};
void Mutate(CopyMe&);
CopyMe fn()
{
CopyMe x;
Mutate(x);
return x;
}
int main()
{
CopyMe y = fn();
return 0;
}
Конструктор копирования объявляется, но не определен, поэтому вызовы к нему не могут быть встроены и исключены. Компиляция с теперь сравнительно старым gcc 4.4 дает следующую сборку в -O3 -fno-inline
(отфильтрован для демонстрации имен С++ и отредактирован для удаления некода).
fn():
pushq %rbx
movq %rdi, %rbx
call CopyMe::CopyMe()
movq %rbx, %rdi
call Mutate(CopyMe&)
movq %rbx, %rax
popq %rbx
ret
main:
subq $1032, %rsp
movq %rsp, %rdi
call fn()
xorl %eax, %eax
addq $1032, %rsp
ret
Как видно, при вызове конструктора копирования нет вызовов. Фактически gcc выполняет эти оптимизации даже при -O0
. Вы должны предоставить -fno-elide-constructors
, чтобы отключить это поведение; если вы это сделаете, gcc генерирует два вызова конструктора копирования CopyMe
- один внутри и один за пределами вызова fn()
.
fn():
movq %rbx, -16(%rsp)
movq %rbp, -8(%rsp)
subq $1048, %rsp
movq %rdi, %rbx
movq %rsp, %rdi
call CopyMe::CopyMe()
movq %rsp, %rdi
call Mutate(CopyMe&)
movq %rsp, %rsi
movq %rbx, %rdi
call CopyMe::CopyMe(CopyMe const&)
movq %rbx, %rax
movq 1040(%rsp), %rbp
movq 1032(%rsp), %rbx
addq $1048, %rsp
ret
main:
pushq %rbx
subq $2048, %rsp
movq %rsp, %rdi
call fn()
leaq 1024(%rsp), %rdi
movq %rsp, %rsi
call CopyMe::CopyMe(CopyMe const&)
xorl %eax, %eax
addq $2048, %rsp
popq %rbx
ret