new
должно вообще исчезнуть (ну, по крайней мере, когда в С++14 пофиксят отсутствие std::make_unique
). Все динамические выделения памяти должны быть инкапсулированы или в стандартную библиотеку, или контейнеры типа std::vector
, или интеллектуальные указатели.
Смарт-указатели стандартной библиотеки могут быть настроены так, чтобы они сами занимались освобождением занимаемой ими памяти. Эта возможность и заложена в основу ответа на вопрос, поставленного в заголовке статьи.
Объект является пересекающим границу динамической библиотеки, если он инициализируется в одном блоке, а используется в другом. Это происходит, когда, например, в dll инициализируется объект и возвращается указатель на него.
Предположим, одна библиотека (или исполнимый модуль) связывается с другой библиотекой, используя фабрику для динамической инициализации объекта и получения указателя на него. Блок, который использует этот указатель, может удалить указатель для освобождения области памяти, на которую он указывает. Если библиотека, которая выделяет память и блок, работающий с указателем, используют различные версии динамического выделения памяти ОС (CRT в Windows), то возникнет ошибка. Пример этой проблемы (в случае с Windows):
Как правило (до появления С++11), разработчики библиотеки должны были разрабатывать функции освобождения памяти для объектов, которые были выделены в пределах этой библиотеки, для того, чтобы избежать этой проблемы. Это имело побочный эффект: интерфейсы таких библиотек становились более «тяжелыми», к тому же от них теперь требовалось «ноу-хау» для корректного выделения и освобождения памяти для объектов библиотеки. В идеале, пользователя не должна была беспокоить сама схема выделения/освобождения, он просто должен был вызывать механизм библиотеки (например, фабрику) для выделения памяти, не заботясь об ее последующем освобождении.
Переходим к коддингу
У нас будет два проекта: первый будет состоять просто из файла main, использующий фабрику библиотеки для инициализации объектов из нее, второй будет иллюстрировать проблемную ситуацию и ее решение.
Проблемная область — синглтон фабрика (ProblematicFactory), которая инициализирует объект и возвращает указатель на него. Решение — другой сингтон, который, после инициализации объекта, возвращает указатель std::unique_ptr
, имеющий свое собственное средство удаления, производящее освобождение памяти в DLL.
Если запустить программу в режиме отладки с определением USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION
, то можно увидеть, что отладчик обнаруживает повреждение «кучи».
Файл main
// main.cpp #include <ProblematicFactory.h> #include <SafeFactory.h> // измените undef на define, чтобы увидеть assert'ы о повреждении кучи #undef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION int main() { #ifdef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION { // это выделение делается в DLL auto wMyObject = ProblematicFactory::getInstance().create(); // это освобождение происходит в текущем блоке delete wMyObject; // если DLL и этот блок будут линковаться с одной и той же CLR DLL, // удаление произойдет нормально, иначе - это вызовет повреждение кучи } #endif { auto wMyObject = SafeFactory::getInstance().create(); // когда программа перейдет в следующий блок, wMyObject будет // автоматически удален, используя пользовательское средство удаления, // реализованное в MyClass.h (см. далее), функция освобождения // в библиотеке вызываться не будет } { std::shared_ptr< MyClass > wMyObject = SafeFactory::getInstance().create(); } return 0; }
Проблемная фабрика
Это — типичная реализация фабрики, которая возвращает указатель на объект, который может быть создан библиотекой.
// ProblematicFactory.h #pragma once #include "DllSwitch.h" #include "MyClass.h" class LIBRARYFACTORY_API ProblematicFactory { public: static ProblematicFactory & getInstance() { static ProblematicFactory wProblematicFactory; return wProblematicFactory; } MyClass * create() const { return new MyClass; } private: ProblematicFactory() {}; };
Безопасная фабрика
Синтаксически, использование этой фабрики точно такое же, как и проблемной (см. main), но здесь указатель инкапсулируется в std::unique_ptr
, а не std::shared_ptr
.
// SaveFactory.h #pragma once #include "DllSwitch.h" #include "MyClass.h" #include <memory> class LIBRARYFACTORY_API SafeFactory { public: static SafeFactory & getInstance(); // Здесь std::unique_ptr не будет пересекать границу библиотеки, // т.к. эта функция не будет входить в DLL. Создание произойдет // на стороне клиента, поэтому используется std::unique_ptr клиента. // Так же, нет необходимости задавать пользовательские средства // удаления, поскольку для класса MyClass существует std::default_delete inline std::unique_ptr< MyClass > create() const { return std::unique_ptr< MyClass >(doCreate()); } private: SafeFactory(); MyClass * doCreate() const; };
Пересечение границы
// MyClass.h #pragma once #include "DllSwitch.h" #include <memory> class LIBRARYFACTORY_API MyClass { }; namespace std { template<> class LIBRARYFACTORY_API default_delete< MyClass > { public: void operator()(MyClass *iToDelete) { delete iToDelete; } }; }
Во всех вышеприведенных файлах подключается заголовочный файл DllSwitch.h
, определяющая LIBRARYFACTORY_API
, его содержание:
// DllSwitch.h #pragma once #ifdef LIBRARYFACTORY_EXPORTS #define LIBRARYFACTORY_API __declspec(dllexport) #else #define LIBRARYFACTORY_API __declspec(dllimport) #endif
ссылка на оригинал статьи http://habrahabr.ru/post/182970/
Добавить комментарий