Зачем мне std :: get_ Contemporary_buffer?

84

С какой целью я должен использовать std::get_temporary_buffer? Стандарт гласит следующее:

Получает указатель на хранилище, достаточное для хранения до n смежных T объектов.

Я думал, что буфер будет размещен в стеке, но это не так. Согласно стандарту C ++ этот буфер на самом деле не временный. Какие преимущества у этой функции перед глобальной функцией ::operator new, которая тоже не создает объекты. Правильно ли я, что следующие утверждения эквивалентны?

int* x;
x = std::get_temporary_buffer<int>( 10 ).first;
x = static_cast<int*>( ::operator new( 10*sizeof(int) ) );

Эта функция существует только для синтаксического сахара? Почему temporaryв его названии?


Один вариант использования был предложен в журнале доктора Добба от 1 июля 1996 г. для реализации алгоритмов:

Если буфер не может быть выделен или он меньше запрошенного, алгоритм по-прежнему работает правильно, он просто замедляется.

Кирилл Васильевич Лядвинский
источник
2
FYI, std::get_temporary_bufferв C ++ 17 не рекомендуется.
Deqing
1
@Deqing Да. Он также будет удален в C ++ 20 и по уважительной причине (как указано ниже). Так что двигайтесь вперед, зритель ..
Никос

Ответы:

44

Страуструп говорит в «Языке программирования C ++» ( §19.4.4 , SE):

Идея состоит в том, что система может держать несколько буферов фиксированного размера готовыми к быстрому распределению, так что запрос пространства для n объектов может дать пространство для более чем n . Однако он также может дать меньше, поэтому один из способов использования get_temporary_buffer()- оптимистично попросить много, а затем использовать то, что оказывается доступным.
[...] Поскольку get_temporary_buffer()он низкоуровневый и, вероятно, будет оптимизирован для управления временными буферами, его не следует использовать в качестве альтернативы new или allocator :: allocate () для получения более длительного хранения.

Он также начинает знакомство с двумя функциями с:

Алгоритмам часто требуется временное пространство для нормальной работы.

... но, похоже, нигде не дает определения временного или долгосрочного .

В анекдоте из «От математики к общему программированию» упоминается, что Степанов, однако, предоставил фиктивную реализацию заполнителя в исходном проекте STL:

К своему удивлению, годы спустя он обнаружил, что все основные поставщики, предлагающие реализации STL, все еще используют эту ужасную реализацию [...]

Георг Фриче
источник
12
Похоже, что реализация этого в VC ++ представляет собой просто вызов цикла operator newс последовательно уменьшающимися аргументами до тех пор, пока распределение не будет выполнено успешно. Никаких особых оптимизаций нет.
jalf
9
То же самое с g ++ 4.5 - кажется, это было сделано из лучших побуждений, но продавцы проигнорировали его.
Георг Фриче
4
Похоже, они должны были обернуть эту функциональность в класс под названиемcrazy_allocator
0xbadf00d
Но давайте будем серьезными - возможно, кто-то будет счастлив выделить много места для хранения. Теперь мы можем попросить систему получить такой огромный объем хранилища - используя get_temporary_buffer. Однако, если мы получаем меньше запрошенной суммы (что было бы очень жаль), мы продолжаем пытаться работать с имеющимся у нас хранилищем. Может быть лучше, чем перехватывать bad_allocисключения, вызванные попыткой выделить больше памяти, чем доступно. Однако настоящая полезность стоит и падает с хорошей реализацией.
0xbadf00d
@jalf Это не рекомендуется в C ++ 17 :) (согласно cppreference.com ).
4LegsDrivenCat
17

Специалист по стандартной библиотеке Microsoft говорит следующее ( здесь ):

  • Не могли бы вы объяснить, когда использовать get_ Contemporary_buffer

Он имеет очень специализированное назначение. Обратите внимание, что он не генерирует исключений, таких как new (nothrow), но также не создает объекты, в отличие от new (nothrow).

Он используется внутри STL в таких алгоритмах, как stable_partition (). Это происходит, когда есть волшебные слова, такие как N3126 25.3.13 [alg.partitions] / 11: stable_partition () имеет сложность «Не более (последний - первый) * журнал (последний - первый) свопов, но только линейное количество свопов, если есть достаточно дополнительной памяти. " Когда появляются волшебные слова «если достаточно дополнительной памяти», STL использует get_porary_buffer (), чтобы попытаться получить рабочее пространство. Если да, то алгоритм может быть реализован более эффективно. Если это невозможно, потому что система работает в опасной близости от нехватки памяти (или задействованные диапазоны огромны), алгоритм может вернуться к более медленной технике.

99,9% пользователей STL никогда не узнают о get_ Contemporary_buffer ().

Джереми
источник
9

Стандарт говорит , что он выделяет для хранения до n элементов. Другими словами, ваш пример может вернуть буфер, достаточно большой только для 5 объектов.

Тем не менее, довольно сложно представить себе хороший вариант использования для этого. Возможно, если вы работаете на платформе с очень ограниченным объемом памяти, это удобный способ получить «как можно больше памяти».

Но на такой ограниченной платформе, я полагаю, вы бы максимально обошли распределитель памяти и использовали бы пул памяти или что-то, что вы полностью контролируете.

Jalf
источник
4

С какой целью я должен использовать std::get_temporary_buffer?

Функция устарела в C ++ 17, поэтому теперь правильный ответ - «ни с какой целью, не используйте ее».

Raedwald
источник
2
ptrdiff_t            request = 12
pair<int*,ptrdiff_t> p       = get_temporary_buffer<int>(request);
int*                 base    = p.first;
ptrdiff_t            respond = p.sencond;
assert( is_valid( base, base + respond ) );

ответ может быть меньше запроса .

size_t require = 12;
int*   base    = static_cast<int*>( ::operator new( require*sizeof(int) ) );
assert( is_valid( base, base + require ) );

фактический размер базы должен быть больше или равен требуемому .

OwnWaterloo
источник
2

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

Таким образом, get_temporary_buffer может быть задуман как кусок памяти большего размера, чем вам нужно, который выделяется один раз (возможно, есть много фрагментов, готовых для приема нескольких запросов), и каждый раз, когда вам нужна память, вы просто получаете один из куски. Таким образом, память не фрагментируется.

Даниэль Муньос
источник
1
Очень интересная мысль. Хотя в настоящее время в большинстве реализаций он, кажется, реализован как « давай-просто-сделай-то-что-работает », он вполне мог бы получить более жесткую поддержку и интегрировать его с остальными процедурами управления памятью. Я голосую за нас, на самом деле ожидая, что это подходит для этого, и комментарии Бьярнеса, кажется, тоже намекают на это.
gustaf r 07
Теперь я посмотрел, что говорит об этом Бьярн, и он говорит, что он разработан для быстрого выделения без инициализации. Таким образом, это было бы похоже на void * operator new (size_t size) только для распределителя (без инициализатора), но это быстрее выделяется, поскольку оно предварительно выделено.
Даниэль Муньос