Почему перераспределение векторной копии вместо перемещения элементов?

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

insert, push_back и emplace (_back) могут привести к перераспределению a std::vector. Я был озадачен, увидев, что следующий код копирует элементы, а не перемещает их при перераспределении контейнера.

#include <iostream>
#include <vector>

struct foo {
    int value;

    explicit foo(int value) : value(value) {
        std::cout << "foo(" << value << ")\n";
    }

    foo(foo const& other) noexcept : value(other.value) {
        std::cout << "foo(foo(" << value << "))\n";
    }

    foo(foo&& other) noexcept : value(std::move(other.value)) {
        other.value = -1;
        std::cout << "foo(move(foo(" << value << "))\n";
    }

    ~foo() {
        if (value != -1)
            std::cout << "~foo(" << value << ")\n";
    }
};

int main() {
    std::vector<foo> foos;
    foos.emplace_back(1);
    foos.emplace_back(2);
}

На моей конкретной машине, использующей мой конкретный компилятор (GCC 4.7), это печатает следующее:

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(1)
~foo(2)

Однако при удалении конструктора копирования (foo(foo const&) = delete;) генерируется следующий (ожидаемый) вывод:

foo(1)
foo(2)
foo(move(foo(1))
~foo(1)
~foo(2)

Почему? Скорее всего, будет более эффективным или, по крайней мере, не менее эффективным, чем копирование?

Следует отметить, что GCC 4.5.1 делает ожидаемую вещь - это регрессия в GCC 4.7 или это какая-то хитроумная оптимизация потому что компилятор видит, что мой объект дешево копировать (но как?!)?

Также обратите внимание, что я убедился, что это вызвано перераспределением, экспериментально помещая foos.reserve(2); перед вставками; это не вызывает ни копирования, ни перемещения для выполнения.

Ответ 1

Короткий ответ: я думаю, что @BenVoigt в основном правильный.

В описании reserve (§23.3.6.3/2) говорится:

Если исключение выбрано иначе, чем конструктор перемещения типа, не относящегося к копированию, нет никаких эффектов.

[И описание resize в п. 23.3.6.3/12 требует того же.]

Это означает, что если T является CopyInsertable, вы получаете сильную защиту от исключений. Чтобы убедиться, что он может использовать только конструкцию перемещения, если она выводит (по неуказанным средствам), что перемещение конструкции никогда не будет бросать. Там нет гарантии, что для этого потребуется или достаточно для этого throw() или noexcept. Если T является CopyInsertable, он может просто выбрать всегда использовать построение копии. В основном, что происходит, так это то, что для стандарта требуется копирование, подобная семантике; компилятор может использовать только конструкцию перемещения в соответствии с правилом as-if, и он может определить, когда или будет ли он использовать эту опцию.

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

Ответ 3

Tip-of-trunk clang + libС++ получает:

foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)

Если вы удалите noexcept из конструктора перемещения, вы получите решение для копирования:

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)