библиотека вызывает у меня глобальные перегрузки new/delete!

Я поддерживаю плагин (реализованный как dll) для большого приложения с закрытым исходным кодом. Это работает нормально в течение многих лет. Однако с последним обновлением SDK поставщик перегрузил глобальные операторы new и delete. Это доставляет мне массу неприятностей. Что происходит, так это то, что мой плагин выделяет строку. Я передаю эту строку в статически связанную библиотеку, которая изменяет ее (меняет ее длину, тем самым перераспределяя ее). Мое приложение вылетает.

Причина, конечно же, в том, что строка находится в пользовательской куче, выделенной поставщиком. Статически связанная библиотека ничего не знает об этой куче и пытается использовать операторы new/delete по умолчанию для этой памяти. Бум.

Теперь вопрос: как я могу сохранить свой код в чистоте и избежать использования операторов поставщика? Макрос условного препроцессора отсутствует. Я не могу избежать включения оскорбительного заголовка, так как он содержит на 2000 строк больше кода, который мне нужен для плагина. Я не могу передать предоставленный распределитель в другую библиотеку, поскольку он не предоставляет для этого никаких механизмов. Я уже звонил продавцу по этому поводу. Я не знаю, что еще я мог бы попробовать?

Дополнение: после жарких дебатов мне удалось убедить поставщика снова удалить перегрузки из следующей версии SDK. Я решил свою насущную проблему, просто взломав текущий SDK и удалив перегрузки вручную. Спасибо за все предложения в этой теме. Они служили аргументами и дальнейшим «доказательством» того, почему перегрузки изначально были плохой идеей.


person BuschnicK    schedule 05.01.2010    source источник
comment
Должны ли вы избегать изменения статической библиотеки?   -  person JoeG    schedule 05.01.2010
comment
Я мог бы изменить его в этом случае, так как это с открытым исходным кодом. Но мне бы очень хотелось избежать обновления моих модификаций с каждым новым релизом. Я также предпочел бы решение, которое работает с библиотеками, к которым у меня нет доступа к исходному коду...   -  person BuschnicK    schedule 05.01.2010
comment
Кроме того, вы говорите о std::string, char * s или о каком-то другом типе строки?   -  person JoeG    schedule 05.01.2010
comment
Вы ничего не сможете сделать, если библиотека перераспределит вашу строку and и не предоставит доступ к своему распределителю. Продолжайте прослушивать их, их библиотека сломана.   -  person Hans Passant    schedule 05.01.2010
comment
char * в этом конкретном случае. Но я думаю, что это на самом деле не имеет значения, поскольку проблема остается для всех распределений.   -  person BuschnicK    schedule 06.01.2010


Ответы (4)


Если вы компилируете (через включение заголовка) переопределенные операторы new/delete, то все вызовы в вашем коде для new/delete будут использовать их. Нет возможности переопределить его (ошибки ссылки) или только частично переопределить и т. д.

Плохой тон вообще переопределять глобальные операторы new/delete. Это плохая идея. Если вы не понимаете, почему это плохая идея, вы не имеете на это права. Если вы понимаете, почему это плохая идея, вы имеете на это право, но, как правило, предпочитаете этого не делать.

Определение глобального нового/удаления экспоненциально более вредно в компоненте, который, как вы ожидаете, люди будут включать непосредственно в свой проект. Ваша задача как клиента — помочь поставщику понять всю серьезность ситуации или перестать быть его клиентом.

Вы можете определить собственный тип распределителя (см. эту ссылку для хороший учебник о том, как это сделать, какой интерфейс необходим и т. д.) и используйте его исключительно с вашими типами STL (это аргумент шаблона).

Для shared_ptr вам нужно сделать что-то немного другое: он принимает объект удаления в качестве параметра конструктора, если вам не нужно поведение по умолчанию «удалить p». Это не пользовательский распределитель; это просто обычный унарный функтор.

person Terry Mahaffey    schedule 05.01.2010
comment
Вы правы в том, что застряли с переопределенным новым и удалением. Однако я не согласен с вашим утверждением о том, что переопределение глобального нового и удаления является дурным тоном. Я делал это и работал над десятками проектов, где это делали другие люди. Как правило, вы НИКОГДА не переопределяете новые и удаляете в библиотеках, потому что это может быть проблемой для ваших пользователей. Вот почему многие хорошие библиотеки следуют соглашению STL, позволяющему пользователям указывать свои собственные распределители для объектов. Но переопределение new и delete очень удобно для управления системными ресурсами, вам просто нужно знать, что вы делаете. - person Beanz; 06.01.2010

Нельзя ли сделать так:

namespace evil{

#include "evil_header.h"

}

Тогда то, что evil_header объявляет как глобальный new/delete, становится evil::new/evil::delete. Я сомневаюсь, что это будет хорошо работать, если в evil_header будут объявлены не-заголовочные определения вещей.

person James    schedule 05.01.2010

Вы можете использовать другое новое в своем пространстве имен:

namespace MyNS {
    // Declare your new/delete operators here
    // and also declare a class implementing the same interface as std::allocator
    // using your newly created memory management functions.
    // Don't forget to put all your classes in the namespace.
    // (if you don't have one already)
}

Затем вы можете использовать все классы STL, указав им свой тип распределителя в качестве аргумента шаблона.

person Joan Rieu    schedule 05.01.2010
comment
Помещение вашего нового/удаленного в ваше собственное пространство имен не гарантирует, что они всегда будут использоваться (типы в этом пространстве имен часто упоминаются внутри других пространств имен). На самом деле, это, вероятно, просто вызовет больше проблем с несоответствием распределителя. Определение нового распределителя для использования с типами STL является ортогональной проблемой, такой тип не имеет ничего общего с глобальным new/delete и не должен находиться в каком-либо конкретном пространстве имен. - person Terry Mahaffey; 05.01.2010
comment
И пользовательские распределители не являются производными от std::allocator. Смотрите ссылку в моем ответе. - person Terry Mahaffey; 05.01.2010
comment
Исправлена ​​ошибка о наследовании. Тем не менее, если операторы new/delete объявлены в пространстве имен, все в этом пространстве имен будет использовать их. Конечно, это не идеально (как вы сказали, другие классы не в этом пространстве имен не будут их использовать). Лучше восстановить оригинальный глобальный новый. Хорошей идеей при перегрузке является использование того же трюка с перегрузкой, который используется в стандартном nothrow new. - person Joan Rieu; 06.01.2010

Один из вариантов — создать свой собственный перегруженный новый оператор, который можно реализовать с помощью malloc.

Это можно определить так:

enum MyNew {EMyNew};

void *operator new(size_t size, MyNew);

Затем это может быть вызвано вами как MyClass* myClass = new (EMyNew)MyClass;

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

person doron    schedule 05.01.2010
comment
К сожалению, это не вариант, так как я не знаю и не контролирую все случаи, когда я использовал new. Подумайте о STL, связанных библиотеках и т. д. - person BuschnicK; 05.01.2010
comment
STL можно обойти с помощью специального распределителя. ‹br/› Связанные библиотеки должны быть безопасными, поскольку они не компилируются с вашими заголовочными файлами. - person doron; 05.01.2010