2009-04-21 2 views
4

Я просто любопытно, если есть элегантный способ решить следующую задачу в C++:C++ удалить объект, на который ссылается два указателя

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

IChannelIn* in; 
IChannelOut* out; 

DummyChannel является одновременно IChannelIn и IChannelOut. Он просто копирует входные данные для вывода. Существует также TCPChannelIn: public IChannelIn и отдельно TCPChannelOut: public IChannelOut.

Теперь, по выбору пользователя, я либо создать один DummyChannel

DummyChannel* d = new DummyChannel; 
in = d; 
out = d; 

или два отдельных объекта: in = new TCPChannelIn; out = new TcpChannelOut

Вопрос заключается в том: что деструктор должен делать?

~App::App() 
{ 
    delete in; 
    delete out; 
} 

завершается с ошибкой, потому что delete in; удалены также фиктивный канал d так что delete out удалений уже удалили вещь.

Есть ли элегантный способ из этого?

ответ

3

Как ваш класс знает, что это указатель фристайла? Например. что говорит против следующего кода?

DummyChannel d; 
in = &d; 
out = &d; 

Это совершенно разумный кусок кода, но ваш крах деструктор будет при попытке удалить либо из указателей.

Краткая история: ресурсы для регенерации - это задача того, кто выделил ресурсы в первую очередь. Если ваш класс получает указатель, класс не может знать и не заботится об освобождении. Это строго ответственность вызывающего.

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

+0

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

3
if (in == out) { 
    delete in; 
} 
else { 
    delete in; 
    delete out; 
} 

?

+0

См. Комментарии к решению ChrisF. Не компилируется. – MSalters

3

Правило большого пальца: сопоставьте свои new с вашим delete.

~App::App() 
{ 
    delete out; 
    // the next line tries to free 
    // memory no longer in the app's 
    // control invoking UB 
    delete in; 
} 

Здесь вы столкнетесь с классической двойной свободной проблемы.

~App::App() 
{ 
    if (in != out) 
    { 
     delete out; 
    } 
    // if in == out the out object is not deleted 
    // for a non trivial object this tantamounts to 
    // leaking resources held by TCPChannelOut other 
    // than d 
    delete in; 
} 

Единственным решением, по-видимому, является использование указателя, посчитанного на умную ссылку.

+2

Это не решит его проблему. Установка 'in' в NULL после его удаления не делает строку' delete out' более актуальной. – Pesto

+0

Что не так с этим ответом? Этот подход работает, когда у вас есть несколько таких общих указателей вместо двух. – dirkgently

+0

Даже если вы удалили и установили его в NULL, то все еще указывает на старые объекты. – danatel

9

Вам нужно что-то вдоль линий:

~App::App() 
{ 
    if (in != out) 
    { 
     delete out; 
    } 
    delete in; 
} 

Как когда каналы разные указатели будут разными.

Однако, как указано в комментариях, in и out не сопоставимы. Безопасным (но неэлегантным) решением было бы использовать флаг dummy. Срабатывает, если in и out устанавливаются на фиктивный канал, деструктор стал бы:

~App::App() 
{ 
    if (!dummy) 
    { 
     delete out; 
    } 
    delete in; 
} 

Хотя я не был бы доволен этим.

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

+1

Нельзя просто сравнивать в выводе, они имеют тип dirrerent (IChannelIn a IChannelOut). Даже их адрес отличается, потому что после in = d указывает на подобъект d. – danatel

+0

Не понимаю, почему из кода, который вы отправили, где в и из обоих установлено значение d (новый DummyChannel). Что-то еще происходит? – ChrisF

+0

DummyChannel является общедоступным IChannelIn и общедоступным IChannelOut. Но IChannelIn и IChannelOut в противном случае не связаны друг с другом. DummyChannel не является родителем IChannelIn, напротив, IChannelIn является родителем DummyChannel. – danatel

7

Если вы используете boost, вы можете рассмотреть возможность использования boost::shared_ptr. Это автоматически удалит объект, когда последний экземпляр будет выпущен.

+0

Я не использую boost в этом проекте. Я буду рассматривать повышение в будущем, но на этот раз не стоит добавлять другую зависимость к моему проекту. – danatel

+0

В этом случае используйте std :: tr1 :: shared_ptr.Это не boost (ну, в некоторых реализациях это boost :: shared_ptr, но в любом случае это часть будущего стандарта). –

+0

Я использую visual studio 2005. Не уверен, что он компилирует tr1. – danatel

0

Я всегда думаю о собственности объекта во время разработки:

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

При попытке ответить на эти вопросы я также определяю способ его реализации (деструкторы, интеллектуальные указатели и т. Д.).

0

Не забудьте сделать ваши деструкторы IChannelIn и IChannelOut виртуальными. Если деструкторы не являются виртуальными, деструкторы TCPChannel или DummyChannel не будут вызываться при вызове деструкторов IChannel (т. Е. При удалении содержимого ptr IChannel).