Исключительная безопасность на арене памяти

Я пишу простой распределитель памяти и сталкиваюсь с небольшой проблемой с безопасностью исключений. Ситуация такова, когда вы выделяете объект, который сам вызывает распределитель. Задача пула памяти - выделить несколько объектов одновременно, а затем удалить их все при уничтожении пула.

{
    MemoryArena m;
    std::string* ptr = m.Allocate<std::string>();
    // use ptr whatever
    // Cleaned up when pool is destroyed
}

Но это становится довольно неприятным, когда он используется несколько раз. Если внутреннее распределение очищено, то оно может быть использовано впоследствии - не плохое предположение, так как это определение пула никогда не удалять объекты, пока не истечет его время жизни. рассматривать:

struct X {
    X(MemoryArena* ptr, std::string*& ref) {
        ref = ptr->Allocate<std::string>();
        throw std::runtime_error("hai");
    }
};
MemoryArena m;
std::string* ptr;
m.Allocate<X>(&m, ptr);
// ptr is invalid- even though it came from the arena 
// which hasn't yet been destroyed

Но если внутреннее распределение не очищено, внешнее распределение также не может быть очищено, потому что область памяти распределяет их линейно, как в аппаратном стеке, поэтому я теряю память. Поэтому я либо нарушаю семантику, рано уничтожая объект, либо теряю память.

Любые предложения о том, как решить эту проблему?

2 ответа

Решение

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

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

Возможно, вы могли бы сделать это явно, с понятием "мягкого" распределения. Не гарантировано, что жить вечно, потому что, будучи "мягким", его можно вернуть обратно на арену. Тогда конструктор X будет делать что-то вроде:

SoftPtr<std::string> tmp(ptr->SoftAllocate<std::string>());
stuff_that_might_throw(); 
ref = tmp.release();

Выполнение деструктора SoftPtr без первого звонка release подразумевает, что никакая ссылка на объект не была выставлена. Он вызывает функцию MemoryArena, которая делает что-то вроде:

  • разрушать объект
  • проверьте, является ли это самое последнее выделение с арены
    • если это так, вычтите размер из текущей позиции указателя арены
    • если нет, больше ничего не делать (память просочилась)

Таким образом, любое количество выделений может быть "отменено", если это сделано в обратном порядке.

По самой семантике пулов памяти и, как вы сами заявили в вопросе, может быть освобожден только пул в целом, а не отдельные объекты. Тем не менее, вы хотите сделать именно это.

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

Другие вопросы по тегам