Настройка std::shared_ptr или boost::shared_ptr для создания исключения при разыменовании NULL

У меня есть несколько проектов, которые широко используют boost::shared_ptr или std::shared_ptr (я могу достаточно скоро перейти на любую из этих реализаций, если для одного есть хороший ответ на этот вопрос, но не для другого). Реализация Boost использует Boost.Assert, чтобы избежать возврата в случае обнаружения пустого (NULL) указателя в operator* или operator-> во время выполнения; в то время как реализация libc++, похоже, не проверяет.

Хотя, конечно, правильность shared_ptr должна быть проверена перед использованием, большая кодовая база со смешанной парадигмой заставляет меня хотеть попробовать вариант, выбрасывающий исключения; так как большая часть кода относительно осведомлена об исключениях и в лучшем случае перейдет в высокоуровневое, но возобновляемое состояние, а не в std::terminate() или segfault.

Как лучше всего настроить эти средства доступа, сохранив при этом надежность shared_ptr? Кажется, что инкапсуляция shared_ptr в throwing_shared_ptr может быть лучшим вариантом, но я опасаюсь нарушить магию. Лучше ли мне скопировать исходный код Boost и просто изменить ASSERTs на соответствующий оператор throw?


Фактическое имя типа, используемое везде для соответствующего типа smart_ptr<T>, представляет собой определение типа, расширенное из макроса; то есть ForwardDeclarePtr(Class) расширяется до чего-то вроде:

class Class;
typedef boost::smart_ptr<Class> ClassPtr;

Все проходит, принимает или сохраняет ClassPtr, так что я могу довольно свободно заменить базовый тип; и я подозреваю, что это устраняет потенциальную проблему нарезки/скрытия.


person rvalue    schedule 17.09.2012    source источник
comment
Вы можете использовать небольшую библиотеку, которую я написал, под названием throwing_ptr. Он оборачивает std::shared_ptr и std::unique_ptr, добавляя исключения в операторы разыменования, если указатель не инициализирован. У него также есть make_shared и make_unique.   -  person rdfm    schedule 28.03.2018


Ответы (2)


На самом деле в std::shared_ptr<T> нет никакой «магии», которая была бы удалена, если бы вы обернули ее внутри пользовательского класса, который выдавал бы исключение при разыменовании общего указателя NULL. Поэтому я не понимаю, почему этот подход не сработает, если ваш новый класс-оболочка следует всей семантике типа std::shared_ptr<T>.

Кстати, вы также можете использовать немного другой подход, а именно создать класс-оболочку, который просто не позволит другим передавать NULL указатели на обернутый std::shared_ptr<T> элемент данных в первую очередь. По сути, это будет класс, который будет применять идиому std::make_shared<T> в своем конструкторе. Я не уверен, основываясь на работе вашего кода, возможно ли это, но это еще один способ обойти проблему, используя подход RAII, а не генерировать исключения.

person Jason    schedule 17.09.2012
comment
Принимаю этот ответ, потому что в комментариях проще объяснить дизайн, безопасность и использование инкапсуляции (классы все время имеют shared_ptr членов). Однако @KevinBallard тоже прав. - person rvalue; 19.09.2012

Просто создайте подкласс std::shared_ptr в throwing_shared_ptr, переопределите эти два метода и заставьте их утверждать и вызывать реализацию std::shared_ptr. Это должно работать нормально, если вы везде используете throwing_shared_ptr вместо того, чтобы нарезать его на std::shared_ptr.

person Lily Ballard    schedule 17.09.2012
comment
Даже нарезка не будет катастрофической проблемой, если производный тип не добавляет никаких элементов данных. - person Mark Ransom; 17.09.2012
comment
@MarkRansom: правильно, но это удалит метательный характер. Конечно, если ваше значение вводится как std::shared_ptr, то я думаю, вы должны ожидать этого в любом случае. - person Lily Ballard; 17.09.2012
comment
@KevinBallard На самом деле это может быть более серьезной проблемой, чем вопрос; что делать, если что-то ожидает простой shared_ptr - есть по крайней мере один случай стороннего кода, который делает это. Я сомневаюсь, что создание исключения через указанную библиотеку будет хуже, чем std::terminate() или SIGSEGV, но оно заслуживает серьезного рассмотрения. - person rvalue; 17.09.2012
comment
Не обращая внимания на вышеизложенное, код библиотеки, конечно, будет принимать параметры или сохранять их в shared_ptr по своему выбору; как уже упоминалось, потенциально нарезка в месте вызова, но не изменение семантики внутреннего кода. - person rvalue; 17.09.2012