Подтвердить что ты не робот

Стандартные библиотечные контейнеры, содержащие множество копий на rvalues ​​в GCC

Я пишу приложение как для Linux, так и для Windows, и заметил, что сборка GCC создает множество бесполезных вызовов для конструктора копирования.

Вот пример кода для создания такого поведения:

struct A
{
    A()                { std::cout << "default" << std::endl; }
    A(A&& rvalue)      { std::cout << "move"    << std::endl; }
    A(const A& lvalue) { std::cout << "copy"    << std::endl; }
    A& operator =(A a) { std::cout << "assign"  << std::endl; return *this; }
};

BOOST_AUTO_TEST_CASE(test_copy_semantics)
{
    std::vector<A> vec_a( 3 );
}

Этот тест просто создает вектор из трех элементов. Я ожидаю 3 вызова конструктора по умолчанию и 0 копий, поскольку нет A lvalues.

В Visual С++ 2010 вывод:

default
move
default
move
default
move

В GCC 4.4.0 (MinGW), (-O2 -std = С++ 0x), вывод:

default
copy
copy
copy

Что происходит и как я могу это исправить? Копии дороги для фактического класса, по умолчанию конструкция и ходы дешевы.

4b9b3361

Ответ 1

Обе версии (Visual С++ 2010 и GCC 4.4.0) ошибочны. Правильный вывод:

default
default
default

Это указано в 23.3.5.1 [vector.cons]/4:

Требуется: T должен быть DefaultConstructible.

Реализация не позволяет предположить, что A является либо MoveConstructible, либо CopyConstructible.

Ответ 2

Похоже, проблема в том, что версия g++, которую у вас есть, не имеет полностью совместимой библиотеки С++ 0x. В частности, в С++ 03 конструктор размера std::vector имеет следующую подпись:

// C++ 03
explicit vector(size_type n, const T& value = T(),
const Allocator& = Allocator());

С этой сигнатурой функции и вашим вызовом создается временная структура, затем привязана константной ссылкой, и ее копии создаются для каждого из элементов.

тогда как в С++ 0x существуют разные конструкторы:

// C++0x
explicit vector(size_type n);
vector(size_type n, const T& value, const Allocator& = Allocator());

В этом случае ваш вызов будет соответствовать первой подписи, а элементы должны быть по умолчанию сконструированы с размещением нового над контейнером (так как @Howard Hinnant правильно указывает в своем ответе компилятор вообще не должен вызывать конструктор перемещения).

Вы можете попробовать и проверить, есть ли в последних версиях g++ обновленная стандартная библиотека, или вы можете обойти проблему, вручную добавив элементы:

std::vector<A> v;
v.reserve( 3 );     // avoid multiple relocations
while (v.size() < 3 ) v.push_back( A() );

Ответ 3

Попробуйте следующее:

std::vector<A> vec_a;
vec_a.reserve(3);
for (size_t i = 0; i < 3; ++i)
  vec_a.push_back(A());

То, что вы пытаетесь сделать, - заставить процесс инициализации использовать конструкцию + перемещение для каждого значения вместо конструкции, а затем скопировать/скопировать/скопировать. Это просто разные философии; авторы библиотек не могли знать, что будет лучшим для любого заданного типа.

Ответ 4

Вы можете добавить специальный (дешевый) кейс, чтобы скопировать алгоритм ctor при копировании построенного по умолчанию объекта в объект "this". Однако это просто обход, но поведение довольно странное. Оба компилятора (библиотеки) создают временный объект в стеке, затем gcc копирует это временное значение в целевые 3 раза; msvc воссоздает временный объект 3 раза (!) (в стеке тоже) и перемещается 3 раза в целевые объекты. Я не понимаю, почему они не создают объекты непосредственно на месте.

Ответ 5

Я думаю, что все 3 варианта не нарушают проект С++ 0x. Для этого требуется следующее: 1. Создает вектор с n значениями инициализированных элементов 2. T должен быть DefaultConstructible 3. Линейная по n

Все 3 варианта удовлетворяют 1, по умолчанию + копия, по умолчанию + ход эквивалентны по умолчанию Все 3 варианта удовлетворяют 3 Все 3 варианта удовлетворяют 2: они работают для типов DefaultConstructible. Конкретный алгоритм может использоваться для типов Moveable. В STL принято использовать разные версии алгоритмов для типов с различными возможностями.