Вставьте немного трассировки в FreeMemory и измените main на это:
int main(int argc, char *argv[]) {
printf("map\n");
std::map<int, vec> z;
printf("vec\n");
vec x;
printf("pair\n");
std::pair<int,vec> y(1,x);
printf("insert\n");
z.insert(y);
printf("inserted 1\n");
y.first = 2;
printf("insert\n");
z.insert(y);
printf("inserted 2\n");
}
Выход:
$ make mapinsert CXXFLAGS=-O3 -B && ./mapinsert
g++ -O3 mapinsert.cpp -o mapinsert
map
vec
pair
getMem n 0 ptr 0x6b0258
insert
getMem n 0 ptr 0x6b0268
getMem n 32 ptr 0x6b0278
getMem n 0 ptr 0x6b02a0
FreeMemory ptr 0x6b0268
inserted 1
insert
getMem n 0 ptr 0x6b0268
getMem n 32 ptr 0x6b02b0
getMem n 0 ptr 0x6b02d8
FreeMemory ptr 0x6b0268
inserted 2
FreeMemory ptr 0x6b0258
FreeMemory ptr 0x6b02d8
FreeMemory ptr 0x6b02b0
FreeMemory ptr 0x6b02a0
FreeMemory ptr 0x6b0278
Итак, из ваших 3 выделений размером 0:
- Один из них — скопировать пустой вектор в пару.
- Один из них — сохранить копию пустого вектора на карте.
Эти два явно необходимы. В чем я не уверен, так это в следующем:
- Один из них — скопировать вектор где-нибудь в вызове
insert
, и он также освобождается при вызове вставки.
Это как если бы insert
(или что-то, что он вызывает внутри) берет свой параметр по значению, а не по ссылке, или insert
явно берет копию в автоматическую переменную за некоторое время до того, как он выделяет новый узел карты. Запуск отладчика в данный момент для меня непростая задача, я оставлю это кому-то другому.
Редактировать: тайна раскрыта. insert
принимает std::pair<const int, vec>
, а не std::pair<int, vec>
. Дополнительная копия пустого вектора связана с тем, что создаваемая вами пара должна быть преобразована в (другой) временный объект, а затем ссылка на этот временный объект передается в insert
. std::pair имеет шаблон конструктора, который позволяет вам обойтись почти всем. 20.2.2/4:
template<class U, class V> pair(const pair<U,V> &p);
Эффекты: инициализирует члены из соответствующих членов аргумента, выполняя неявные преобразования по мере необходимости.
Я также заметил, что в моей реализации vec x;
не вызывает getMem
, а vec x(0);
вызывает. Итак, на самом деле:
z[1] = vec();
Является меньшим количеством кода и лишает вас возможности сделать дополнительную копию (хотя вместо этого вызывает operator=
). Он по-прежнему делает 2 0-размерных выделений, по крайней мере, для меня.
Стандарт C++ определяет, что operator[]
возвращает результат указанного выражения, включающего вызов insert
. Я не уверен, означает ли это, что эффекты operator[]
"как если бы" были вызваны make_pair
и insert
(то есть стандарт так же хорош, как и указание источника для operator[]
), или просто возвращаемое значение является такое же значение, как заданное выражение. Если последнее, то, возможно, реализация могла бы сделать это с помощью одного выделения размером 0. Но, конечно, map
не имеет гарантированного способа создать запись без предварительного создания пары, содержащей сопоставленный тип, поэтому следует ожидать 2 выделения. Или, точнее, 2 копии желаемого сопоставленного значения: тот факт, что копирование вектора размера 0 приводит к выделению размера 0, зависит от реализации.
Итак, если у вас был случай, когда значение было очень дорогим для копирования, но очень дешевым для создания по умолчанию (например, контейнер с большим количеством элементов), то следующее может быть полезным:
std::map<int, vec> z;
vec x(1000);
z[1] = x;
// i.e. (*(z.insert(std::pair<const int, vec>(1,vec())).first)).second = x;
делает 2 выделения размера 4000 и 2 размера 0, тогда как:
std::map<int, vec> z;
vec x(1000);
z.insert(std::pair<const int, vec>(2, x));
составляет 3 размера 4000 и ни одного размера 0. В конце концов размер становится достаточно большим, чтобы дополнительное выделение в первом коде было дешевле, чем дополнительное копирование во втором коде.
Возможно, в этом помогут move-конструкторы в C++0x, я не уверен.
person
Community
schedule
09.03.2010