Использование shared_ptr в dll-интерфейсах

У меня есть абстрактный класс в моей DLL.

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

я хочу получить IBase в моем exe-файле, который загружает dll. Первый способ - создать следующую функцию

IBase * CreateInterface();

и добавить виртуальную функцию Release() в IBase,

Второй способ заключается в создании другой функции

boost::shared_ptr<IBase> CreateInterface();

и нет Release() функция нужна.

Вопросы.

1) Верно ли, что освобождение деструктора и памяти вызывается в dll (а не в exe-файле) во втором случае?

2) Хорошо ли работает второй случай, если exe-файл и dll были скомпилированы с разными компиляторами (или разными настройками).

4 ответа

Ответ на ваш первый вопрос: виртуальный деструктор в вашей dll называется - информация о его местонахождении встроена в ваш объект (в vtable). В случае освобождения памяти это зависит от того, насколько дисциплинированны пользователи вашего IBase являются. Если они знают, что они должны позвонить Release() и учтите, что исключение может обойти поток управления в неожиданном направлении, будет использовано правильное.

Но если CreateInterface() возвращается shared_ptr<IBase> он может связать правильную функцию освобождения прямо с этим умным указателем. Ваша библиотека может выглядеть так:

Destroy(IBase* p) {
    ... // whatever is needed to delete your object in the right way    
}

boost::shared_ptr<IBase> CreateInterface() {
    IBase *p = new MyConcreteBase(...);
    ...
    return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
}                                         // which is called instead of a plain
                                          // delete

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

Чтобы ответить на ваш второй вопрос: Недостатки этого подхода четко сформулированы другими ответами: ваша аудитория должна использовать тот же компилятор, компоновщик, настройки, библиотеки, что и вы. И если их может быть довольно много, это может стать серьезным недостатком для вашей библиотеки. Вы должны выбрать: безопасность против большей аудитории

Но есть возможная лазейка: используйте shared_ptr<IBase> в вашем приложении, т.е.

{
    shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
    ...
    func();
    ...
}

Таким образом, никакой специфичный для реализации объект не передается через границу DLL. Тем не менее ваш указатель надежно спрятан за shared_ptr, кто звонит DestroyFromLibrary в нужное время, даже если func() Выкидываю исключение или нет.

Я бы посоветовал не использовать shared_ptr в интерфейсе. Даже использование C++ вообще в интерфейсе DLL (в отличие от процедур "extern C" only) проблематично, поскольку искажение имен не позволит вам использовать DLL с другим компилятором. С помощью shared_ptr Это особенно проблематично, потому что, как вы уже определили, нет гарантии, что клиент DLL будет использовать ту же реализацию shared_ptr как звонящий. (Это потому что shared_ptr является классом шаблона, и реализация полностью содержится в заголовочном файле.)

Чтобы ответить на ваши конкретные вопросы:

  1. Я не совсем уверен, что вы спрашиваете здесь... Я предполагаю, что ваша DLL будет содержать реализации классов, полученных из IBase, Код для их деструкторов (так же как и остальной код), в обоих ваших случаях, будет содержаться в DLL. Однако, если клиент инициирует уничтожение объекта (путем вызова delete в первом случае или позволяя последнему экземпляру shared_ptr выйти из области видимости во втором случае), тогда деструктор будет вызван из клиентского кода.

  2. Имена имен обычно не позволяют использовать вашу DLL с другим компилятором в любом случае... но реализация shared_ptr может измениться даже в новой версии того же компилятора, и это может привести к неприятностям. Я бы уклонился от использования второго варианта.

  1. С помощью shared_ptr удостоверится, что функция освобождения ресурса будет вызвана в DLL.
  2. Посмотрите на ответы на этот вопрос.

Выходом из этой проблемы является создание чистого интерфейса C и тонкой полностью встроенной оболочки C++.

На ваш первый вопрос: я делаю обоснованное предположение и не говорю из опыта, но мне кажется, что во втором случае освобождение памяти будет называться "в.exe". Есть две вещи, которые происходят, когда вы звоните delete object;Во-первых, вызывается деструктор, а во-вторых, освобождается память для объекта. Первая часть, вызов деструктора, определенно будет работать, как вы ожидаете, вызывая правильные деструкторы в вашей dll. Однако, так как shared_ptr является шаблоном класса, его деструктор генерируется в вашем.exe, и поэтому он будет вызывать оператор delete() в вашем exe, а не тот, что в.dll. Если они были связаны с разными версиями времени выполнения (или даже статически связаны с одной и той же версией времени исполнения), это должно привести к ужасному неопределенному поведению (в этой части я не совсем уверен, но это кажется логичным), Есть простой способ проверить, является ли то, что я сказал, правдой - переопределить глобальный оператор delete в вашем exe, но не в dll, поставить точку останова в нем и посмотреть, что вызывается во втором случае (я бы сделал это сам, но я так много времени для отдыха, к сожалению).

Обратите внимание, что для первого случая существует то же самое (вы, кажется, это понимаете, но на всякий случай). Если вы делаете это в exe:

IBase *p = CreateInterface();
delete p;

тогда вы находитесь в том же самом операторе вызова, новом в dll, и вызывающем операторе удаления в exe. Вам понадобится либо соответствующая функция DeleteInterface(IBase *p) в вашей dll, либо метод Release() в IBase (который не должен быть виртуальным, просто не встроенным) для единственной цели вызова нужной памяти функция освобождения.

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