2010-10-14 5 views
5

У меня есть код, который использует много указателей, указывающих на один и тот же адрес. Учитывая эквивалентный простой пример:Как безопасно удалить несколько указателей

int *p = new int(1); 
int *q = p; 
int *r = q; 

delete r; r = NULL; // ok 
// delete q; q = NULL; // NOT ok 
// delete p; p = NULL; // NOT ok 

Как безопасно удалить его без многократного удаления? Это особенно сложно, если у меня много объектов, которые имеют указатели, указывающие на один и тот же адрес.

+1

Не должно ли это работать? delete null указывается в стандарте, поэтому он разрешен и должен работать. Хорошо, это не лучший стиль кодирования ... –

+0

@Mario: Удаление NULL задается как NO-OP, но вызов его накладывает некоторые накладные расходы. –

+6

Задача q и p не будет NULL, поэтому будут двойные удаления. –

ответ

18

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

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

Просто помните, что ваши звонки на new должны быть сбалансированы по вашим звонкам до delete. Каждый раз, когда вы выделяете память, вы знаете, что вы должны написать балансировочный код (часто деструктор), чтобы освободить эту память.

+0

IMO это лучший ответ. –

+2

+1: Мое мнение таково, что shared_ptr следует использовать только в случае с угловым корпусом, где нет ясного владельца памяти, и что в большинстве случаев вам это не нужно. – n1ckp

+0

Пожалуйста, прочтите: http://crazyeddiecpp.blogspot.com/2010/12/pet-peeve.html - вы не удаляете указатели! –

28

Ваш инструмент: shared_ptr из библиотеки boost. Посмотрите на документацию: http://www.boost.org/doc/libs/1_44_0/libs/smart_ptr/shared_ptr.htm

Пример:

void func() { 
    boost::shared_ptr<int> p(new int(10)); 
    boost::shared_ptr<int> q(p); 
    boost::shared_ptr<int> r(q); 

    // will be destructed correctly when they go out of scope. 
} 
+3

+1 - сценарий OP «много указателей, указывающих на один и тот же адрес» идеально подходит для 'shared_ptr' –

+1

Недавно выпущенные компиляторы предоставляют shared_ptr как член пространства имен tr1. Поэтому boost не требуется - вместо этого вы можете использовать tr1 :: shared_ptr. – nobar

+1

Указатели «Raw» в C++ унаследованы от C, но проблема в том, что они вообще не выражают права собственности (это одна из проблем, о которых нельзя сказать достаточно часто, IMHO). Одна из самых важных особенностей кода заключается в том, что он должен выражать намерения автора (включая право собственности), поэтому всегда лучше использовать интеллектуальные указатели boost или, по крайней мере, ** std :: auto_ptr **. (Но указатели для старой школы подходят для простых случаев, например для внутренних объектов для объектов с тривиальным поведением). – riviera

10

«Современная» ответ на использование смарт-указатель, и не делать каких-либо ручных удалений.

boost::shared_ptr<int> p(new int(1)); 
boost::shared_ptr<int> q = p; 
boost::shared_ptr<int> r = q; 

Конец истории!

+0

+1 для завершения истории ';)' –

+0

-1 для публикации непроверенного кода. Хороший API-интерфейс с умными указателями (например, std :: auto_ptr of boost :: shared_ptr) не позволяет назначать указатели на интеллектуальные указатели или создавать интеллектуальные указатели, неявные из указателей, используя конструктор копирования. таким образом, владение не будет очевидным. Измените свою первую строку, чтобы повысить :: shared_ptr p = boost :: shared_ptr (новый int (1)); –

+0

спасибо, код исправлен –

0

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

Представьте, что у вас есть std::vector<whatever*>, а некоторые указатели whatever* указывают на тот же объект. Безопасная очистка включает в себя обеспечение того, что вы не удаляете одно и то же дважды - создайте std::set<whatever*> по ходу дела и удалите указатели, которые еще не установлены. После удаления всех объектов, отмеченных указателями, оба контейнера можно безопасно удалить.

Возвращаемое значение от insert можно использовать для определения того, был ли введенный элемент новым или нет. Я не проверял следующее (или используется станд :: установить на некоторое время), но я думаю, что следующее права ...

if (myset.insert (pointervalue).second) 
{ 
    // Value was successfully inserted as a new item 
    delete pointervalue; 
} 

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

3

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

Как только вы знаете, кто владеет памятью, вернитесь к коду и выполните его.Если объект является единственным ответственным за другой объект, он должен удерживать его с помощью интеллектуального указателя с одним владельцем (std::auto_ptr/unique_ptr) или даже с необработанным указателем (старайтесь избегать этого, поскольку это общий источник ошибок) и управляет памяти вручную. Затем передайте ссылки или указатели на другие объекты. Когда собственность передается, используйте объекты интеллектуального указателя, чтобы предоставить объект новому владельцу. Если владение действительно разделено (нет четкого владельца выделенного объекта), вы можете использовать shared_ptr и позволить смарт-указателю иметь дело с управлением памятью).

1

Почему вы пытаетесь удалить указатели произвольно? Каждый динамически выделенный объект распределяется по адресу один место, владелец. И должно быть, что одна ответственность владельцев за обеспечение того, чтобы объект был удален снова.

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

И иногда вы просто хотите забыть о владении и использовании shared Владелец: каждый, кто использует объект shares ownerip, и до тех пор, пока существует хотя бы один пользователь, объект не следует удалять.

Затем вы используете shared_ptr.

Одним словом, используйте RAII. Не пытайтесь вручную удалять объекты.

0

Невозможно узнать, была ли удалена память, на которую ссылается указатель, как в вашем случае.
Если вы не можете использовать библиотеку с интеллектуальными указателями, которые ссылаются на подсчет ссылок, и вы не можете реализовать свою собственную схему подсчета ссылок (если вам действительно нужно делать то, что вы описали в своем сообщении), попробуйте вызвать realloc на указатели.
Я прочитал в других сообщениях, что в зависимости от реализации вызов realloc может не сбой, а возврат нулевого указателя. В этом случае вы знаете, что память, на которую ссылается этот указатель, была освобождена.
Если это работает как грязное решение, оно не будет переносимым, но если у вас нет другого выбора, попробуйте. Хуже, конечно, будет сбой вашего приложения :)

0

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

void safeDelete(void **ptr) 
{ 
    if(*ptr != NULL) 
    { 
    delete ptr; 
    *ptr = NULL; 
    } 
} 

Я не уверен, если я сделал это 100% правильно, но то, что вы делаете, вы передаете указатель на этот метод, который принимает указатель указатель, проверяет, чтобы указатель, на который он указывает, не был установлен в NULL, затем удаляет объект, а затем устанавливает адрес в 0 или NULL. Поправьте меня, если это не очень хороший способ сделать это, я также новичок в этом, и кто-то сказал мне, что это отличный способ проверить, не усложнившись.

+0

Это не решает проблему; с примером в вопросе 'safeDelete (& r)' будет работать, но он не будет изменять 'q' или' p', поэтому 'safeDelete (& q)', ​​скорее всего, произойдет сбой. – Synxis

Смежные вопросы