Контейнеры стандартной библиотеки, производящие много копий на значениях в GCC - PullRequest
22 голосов
/ 01 февраля 2011

Я пишу приложение для 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 элементов.Я ожидаю 3 вызова конструктора по умолчанию и 0 копий, поскольку A lvalues ​​отсутствует.

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

default
move
default
move
default
move

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

default
copy
copy
copy

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

Ответы [ 5 ]

18 голосов
/ 01 февраля 2011

Обе реализации (Visual C ++ 2010 и GCC 4.4.0) имеют ошибки.Правильный вывод:

default
default
default

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

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

Реализациянельзя допускать, чтобы A было MoveConstructible или CopyConstructible.

6 голосов
/ 01 февраля 2011

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

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

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

в то время как в C ++ 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() );
1 голос
/ 01 февраля 2011

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

1 голос
/ 01 февраля 2011

Попробуйте это тогда:

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

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

0 голосов
/ 08 февраля 2011

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

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

...