Может ли компилятор удалить следующую копию?

Я все еще новичок-программист, я знаю, что преждевременная оптимизация плохая, но я также знаю, что копирование огромного материала вокруг тоже плохо.

Я прочитал о копировании 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