Исключительная безопасность на арене памяти
Я пишу простой распределитель памяти и сталкиваюсь с небольшой проблемой с безопасностью исключений. Ситуация такова, когда вы выделяете объект, который сам вызывает распределитель. Задача пула памяти - выделить несколько объектов одновременно, а затем удалить их все при уничтожении пула.
{
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 функциями для получения и установки следующего адреса выделения. Это дает вам низкоуровневый механизм, который вы можете использовать для создания того, что вы хотите поверх него.