2016-11-07 4 views
6

Я работаю над некоторым пространством памяти с пользовательским назначением и удалением, которые происходят с использованием malloc-подобного интерфейса, который я не контролирую (т. Е. Выделяют n байтов или освобождают выделенный ptr). Итак, ничего подобного delete [].Как использовать новое место размещения с помощью API пользовательского распределения?

Теперь я хочу построить массив T. Я получаю пространство для этого с auto space_ptr = magic_malloc(n*sizeof(T)). Теперь я хочу сделать что-то вроде array-placement-new, чтобы построить n элементов на месте. Как мне это сделать? ... или я должен просто переходить от 1 к n и строить одиночные T?

Примечание:

  • я игнорирую вопросы выравнивания здесь (или, скорее, предполагая, что alignof(T) делит sizeof(T)). Если вы хотите настроить выравнивание, это будет еще лучше, но вы можете игнорировать его для простоты.
  • C++ 11 приветствие (желательно, по сути), но не C++ 14/17.
+0

Существует версия размещения новых для массивов ... но могут быть проблемы с выравниванием. Это [это] (http://coliru.stacked-crooked.com/a/01699890c4ae1ac0), что вам нужно? – AndyG

+0

@AndyG: Вероятно, нет, потому что для размещения 'new []' используется некоторое пространство для записи информации, используемой 'delete []'. – einpoklum

ответ

5

Предполагаю, что ваша память достаточно выровнена для вашего T. Вероятно, вы хотите это проверить.

Следующая проблема есть исключения. Мы действительно должны написать две версии, одна из которых может заключаться в том, что построение вызовет исключение, а одно без него.

Я напишу исключительную безопасную версию.

template<class T, class...Args> 
T* construct_n_exception_safe(std::size_t n, void* here, Args&&...args) { 
    auto ptr = [here](std::size_t i)->void*{ 
    return static_cast<T*>(here)+i; 
    }; 
    for(std::size_t i = 0; i < n; ++i) { 
    try { 
     new(ptr(i)) T(args...); 
    } catch(...) { 
     try { 
     for (auto j = i; j > 0; --j) { 
      ptr(j-1)->~T(); 
     } 
     } catch (...) { 
     exit(-1); 
     } 
     throw; 
    } 
    } 
    return static_cast<T*>(here); 
} 

и не исключение безопасный вариант:

template<class T, class...Args> 
T* construct_n_not_exception_safe(std::size_t n, void* here, Args&&...args) { 
    auto ptr = [here](std::size_t i)->void*{ 
    return static_cast<T*>(here)+i; 
    }; 
    for(std::size_t i = 0; i < n; ++i) { 
    new (ptr(i)) T(args...); 
    } 
    return static_cast<T*>(here); 
} 

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

C++ 17 предоставляет некоторые новые функции для выполнения именно этих задач. Они, вероятно, справляются с угловыми делами, которые у меня нет.


Если вы пытаетесь подражать new[] и delete[], если T имеет нетривиальное dtor вам придется вставлять сколько T вы создали в блоке.

Типичный способ сделать это - попросить дополнительную комнату для счета на спереди блока. Т.е., запросите sizeof(T)*N+K, где K может быть sizeof(std::size_t).

Теперь в вашем new[] эмуляторе, N в первый бит, а затем позвоните construct_n на блок сразу после него.

В delete[] вычтите из переданного в указателе sizeof(std::size_t), прочитайте N, а затем уничтожьте объекты (справа налево до заказа на строительство зеркала).

Все это потребует тщательной оценки try - catch.

Если, однако, ~T() тривиальна, как ваш эмулировать new[] и delete[] не храним, что дополнительные std::size_t и они не читали его.

(Обратите внимание, что это как подражатьnew[] и delete[]. Как именно new[] и delete[] работа зависит от реализации. Я просто набрасывать один способ, которым Вы можете эмулировать их, не могут быть совместимы с тем, как они работают в вашей системе. Например, некоторые АБИСЫ всегда могут хранить N даже если ->~T() тривиален, или множество других вариаций.)


Как отмечают О.П., вы также можете проверить тривиальное строительство до беспокоясь об этом.

+0

Насколько мы уверены, что это будет оптимизировано для T с тривиальным конструктором? Кроме того, зачем выходить (-1) на уничтожение? – einpoklum

+0

@einpoklum Потому что мы уже раздаем бросок в этот момент. Строительство бросило. Деструктор бросил. Мы в основном завинчены. Я не уверен, что он оптимизирован; Я также проверял бы это: но я бы сначала применил вышеизложенное и подтвердил, что это вызывает ненужную петлю. По крайней мере, это обнаружение и пропуски тривиального бита сделает отладку менее смешной. :) Вы бы использовали диспетчеризацию тегов и 'std :: is_trivial' или somesuch. – Yakk

+1

Будьте осторожны при размещении 'size_t' в начале выделения. Само распределение обычно выровнено, но дополнительный 'size_t' может привести к несоосности. Я столкнулся с этой проблемой в реальном приложении, где clang использовал SIMD для инициализации двух удвоений в классе, требуя полного 'alignof (max_align_t)', гарантированного StdLib 'new' /' malloc', тогда как 'sizeof (size_t) dyp