До появления умных указателей

До того, как появились интеллектуальные указатели (способные взять на себя ответственность за ресурсы в динамической области и освободить их после использования), мне интересно, как ведется учет динамически создаваемых объектов, когда они передаются в качестве аргументов функциям, которые используют указатели ресурсов.

Под бухгалтерией я имею в виду, что если есть «новый», то через некоторое время после него должно быть «удалить». В противном случае программа будет страдать от утечки памяти.

Вот пример, где B — это класс, а void a_function (B *) — сторонняя библиотечная функция:

void main() {

B* b = new B(); // line1

a_function(b);  // line2

???             // line3
}

Что мне делать в строке 3? Предполагаю ли я, что сторонняя функция позаботилась об удалении памяти? Если этого не произошло, и я предполагаю, что это так, то моя программа страдает от утечки памяти. Но, если он освобождает память, занятую b, и я тоже делаю это в main (), чтобы быть в безопасности, тогда b фактически освобождается дважды! Моя программа вылетает из-за двойной ошибки!

2

Решение

Хорошо, оставайтесь в стороне от предстоящей дискуссии о том, почему это не актуально, и вы все равно должны использовать умные указатели …

При прочих равных условиях (никаких пользовательских распределителей или чего-либо подобного) правило тот, кто выделяет память, должен освобождать память. Сторонние функции, такие как в вашем примере, должны абсолютно никогда освободить память, которую он не создавал, главным образом потому, что 1) это плохая практика в целом (ужасный запах кода) и, что более важно, 2) он не знает, как память была выделена для начала. Представьте себе следующее:

int main()
{
void * memory = malloc(sizeof(int));
some_awesome_function(memory);
}

// meanwhile, in a third-party library...

void some_awesome_function(void * data)
{
delete data;
}

Что будет, если malloc/free а также new/delete работают с использованием разных распределителей? Вы смотрите на какую-то потенциальную ошибку, потому что распределитель используется для delete понятия не имеет, что делать с памятью, которая была выделена mallocРаспределитель. Вы никогда free память, которая была newи ты никогда delete память, которая была malloc«D. Когда-либо.

Что касается первого пункта, тот факт, что вы должны спросить, что произойдет, если сторонняя библиотека освободит память, а вы попытались (или не попытались) освободить ее вручную, это именно то, почему так не следует делать. потому что у тебя просто нет возможности узнать. Таким образом, общепринятая практика заключается в том, что любая часть кода отвечает за выделение, а также за освобождение. Если все придерживаются этого правила, каждый может отслеживать свою память, и никто не остается угадывать.

2

Другие решения

Две основные языковые функции, которые включают «умные указатели» и, в более общем смысле, идиому управление ресурсами в пределах области видимости (SBRM, иногда также ономатопоэтически называемый RAII, для «получения ресурсов является инициализацией»), это:

  • деструкторы (автоматические gotos)

  • неограниченные переменные (каждый объект может появляться как переменная)

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

[Между прочим, эти две особенности означают, что goto является необходимо в C для систематического, общего распределения ресурсов и множественных выходов, в то время как они по существу запрещено в C ++. С ++ поглощает goto в основной язык.]

Как и с любым языком, людям требуется много времени, чтобы выучить, понять и принять «правильные» идиомы. Особенно учитывая исторические связи C ++ с C, многие программисты, которые работали и работают над проектами C ++, пришли из опыта C и, по-видимому, сочли более удобным придерживаться знакомых шаблонов, которые все еще поддерживаются C ++, даже если они не рекомендуется (просто заменить malloc с new все, и мы будем готовы к отправке «).

5

Вы уничтожаете то, что создаете, библиотека уничтожает то, что создает.

Если вы делитесь данными с библиотекой (например, char * для данных файла), в документации библиотеки будет указано, хранит ли она ссылку на ваши данные (в этом случае не удаляйте вашу копию, пока библиотека не использует ее) или делает копию ваших данных (в этом случае задача библиотеки — удалить данные после завершения).

0

Я вижу много людей, указывающих на то, что умные указатели существовали с самого начала C ++. Но дело в том, что не весь код использует их даже сегодня. Распространенным подходом является подсчет ссылок вручную:

void main() {
B* b = createB(); //refcount = 1
a_function(b);
releaseB(b); //--refcount
}

void a_function(B* b) {
acquireB(b); //refcount++ when we store the reference somewhere
...
}
0

умные указатели — это способ облегчить реализацию политики. Были использованы те же политики (приписывающие ответственность за удаление одному владельцу или их ряду). Вы просто должны были документировать политику и не забывать действовать соответствующим образом. Умные указатели — это способ документировать выбранную политику и одновременно ее реализовывать. В вашем случае вы посмотрели документацию по a_function и увидели, чего она требует. Или сделал более или менее обоснованное предположение, если оно не было задокументировано.

0

Ответ есть в документации сторонней функции a_function(), Возможные случаи могут быть:

  • функция просто использует данные в объекте и не будет сохранять ссылки на нее после завершения вызова функции (пример: printf). Вы можете безопасно удалить объект после завершения вызова функции.
  • функция (в некотором внутреннем объекте библиотеки) будет сохранять ссылку на объект до более позднего вызова (скажем, b_function()). Вы несете ответственность за удаление объекта, но должны сохранять его живым, пока не позвоните b_function (пример:strtok).
  • функция становится владельцем объекта и не гарантирует существование объекта после его вызова (пример: free()). В этом случае в документации обычно указывается, как создать объект (malloc, new, my_library_malloc).

Это всего лишь несколько примеров множества разных поведений, которые могут быть возможны, но пока функция задокументирована достаточно хорошо, вы сможете делать правильные вещи.

0

Что мне делать в строке 3?

Вы консультируетесь с документацией a_function, Обычное правило заключается в том, что функции делают ничего такого о собственности или жизни, если они не говорят, что они делают. Потребность в такой документации довольно четко установлена ​​с помощью API C, где умные указатели недоступны.

Так что, если он не говорит, что удаляет свой параметр, то это не так. Если он не говорит, что хранит копию своего параметра за пределами времени, когда он возвращается, до какого-то другого указанного времени, то он этого не делает.

Если он что-то говорит, вы действуете соответственно, а если он ничего не говорит, тогда вы delete b (или желательно ты пишешь B b; a_function(&b); вместо этого — заметьте, что, не уничтожая объект, функции не нужно заботиться о том, как вы Создайте объект, вы можете решать).

Надеемся, что он говорит все, что говорит явно, но если вам не повезло, он говорит это по некоему соглашению о том, что определенные виды функций в API принимают права на объекты, на которые ссылаются их параметры. Например, если это называется set_global_B_instance тогда у вас может появиться скрытое подозрение, что этот указатель будет сохранен, и удаление его сразу после установки будет неразумным.

Если это ничего не говорит в любом случае, но ваш код в итоге глючит, и вы в конечном итоге обнаружите, что a_function называется delete на его аргумент, то вы найдете тот, кто документально a_function и ты ударить их в нос представить ошибку в их документации.

Зачастую этот человек оказывается самим собой, и в этом случае пытаются усвоить урок — документ, подтверждающий право собственности на объект.

Помимо того, что они помогают избежать ошибок кодирования, интеллектуальные указатели обеспечивают некоторую степень самодокументирования для функций, которые принимают или возвращают указатели в тех случаях, когда существуют проблемы с владением. При отсутствии самодокументирования у вас есть актуальная документация. Например, если функция возвращает auto_ptr вместо необработанного указателя, который говорит вам delete должен быть вызван по указателю. Вы можете позволить auto_ptr сделайте это для вас, или вы можете назначить его другому интеллектуальному указателю, или вы можете release() указатель и управляй им сам. Функция, которую вы вызвали, не волнует и не должна ничего документировать. Если функция возвращает необработанный указатель, то она должна сообщить вам что-то о времени жизни объекта, на который указывает указатель, потому что у вас нет возможности угадать.

0

Просто посмотрите на C APIs для подсказок. C API довольно часто предоставляют явные функции создания и уничтожения. Они обычно следуют некоторому формальному соглашению об именах в библиотеках.

Используя ваш пример, это будет плохой дизайн, если a_function удаляет / освобождает параметр, если он не был явно помечен как функция уничтожения (в этом случае вы не должны использовать этот параметр после вызова функции. В большинстве случаев неверно предполагать, что безопасно уничтожать объекты, которыми вы не владеете. Конечно, с помощью интеллектуальных указателей механизмы владения, время жизни и очистка часто обрабатываются интеллектуальным указателем, где это возможно.

Так что да, люди использовали new а также deleteи хотя я раньше не писал C ++ templates — было бы чаще видеть явное new а также delete в программах. Умные указатели не очень хорошие средства для передачи объектов и передачи прав собственности без templates — которые, наряду с исключениями, были введены примерно в 1990 году (через 7 лет после выпуска C ++). Естественно, компиляторам потребовалось некоторое время для поддержки всех этих функций, а людям — реализовать контейнеры и улучшить эти реализации. Обратите внимание, что это было возможно до шаблонов, но не всегда практично реализовывать / клонировать контейнер для произвольных типов, потому что язык не поддерживал дженерики задолго до шаблонов. Конечно, конкретный класс с конкретными типами может легко выполнить механику интеллектуальных указателей, где тип был инвариантен в те дни … но это приводит к формам дублирования кода, когда непатентованные средства недоступны.

Но даже сегодня, это необычный дизайн для объекта содержимого параметра смарт-указателя, который должен быть заменен или уничтожен, если не будет четко обозначено. Вероятность этого также уменьшается, потому что в качестве параметра передается умный указатель, а не объект, который он содержит. Таким образом, количество ошибок, связанных с памятью, уменьшилось с тех пор, но все же следует соблюдать некоторые меры предосторожности и добросовестного владения.

0
По вопросам рекламы [email protected]