Проблема порядка разложения в базовом классе C ++ - PullRequest
2 голосов
/ 20 июля 2010

Кто-нибудь знает какой-нибудь трюк, который я мог бы использовать, чтобы сохранить класс Derived до тех пор, пока не будет вызван деструктор базового класса?

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

Причина, по которой мне нужно что-то вроде этого, в том, что у меня есть собственный класс Event (сигнал / слот).

Класс Event предоставляет класс Observer.

Если я определяю:

class A : public Event::Observer

, а затем удаляю экземпляр класса A, когда ~ Observer автоматически удаляет любой сигналподключен к этому наблюдателю.

Но поскольку класс A уничтожается перед наблюдателем, если что-то в другом потоке вызывает слот на A после ~ A и до вызова ~ Observer.Все идет к черту ...

Я всегда могу вызвать метод Observer.release из ~ A, который решает проблему синхронизации.Но было бы чище, если бы мне это не понадобилось.

Есть идеи?

Ответы [ 6 ]

4 голосов
/ 20 июля 2010

Вы определенно не хотите менять порядок уничтожения, что хорошо, потому что вы не можете.

Что вы действительно хотите сделать, так это уничтожить / отключить / закрыть Обозреватель.

Что бы я сделал, это добавил бы это к вашему классу Event :: Observer:


void Event::Observer::Shutdown()
{
    if(!isShutdown)
    {
        //Shut down any links to this observer
        isShutdown = true;
    }
}

void ~Event::Observer()
{
    Shutdown();
    //rest of Event::Observer destruction
}

и затем:


~A()
{
    Shutdown();
    //clean up any other A resources
}

Если бы вы сделали что-то вроде IDisposable, предложенное Дэвидом, этоработать тоже - просто вызовите Observer::Dispose() в своем деструкторе для класса A.

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

2 голосов
/ 20 июля 2010

Я предлагаю вам либо реализовать подсчет ссылок, либо интерфейс IDisposable и использовать его в качестве соглашения среди ваших клиентов. Независимо от того, вызываете ли вы Observer::Release() в вашем A::dtor(), вы говорите о том, чтобы другой поток зашел и вызвал метод для объекта, который уничтожается. Это определенно условие гонки, вы никогда не должны иметь код из другого потока , выполняемый асинхронно для метода объекта, который уничтожается.

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

2 голосов
/ 20 июля 2010

Деструкторы работают так, как от них ожидают, и вы не должны их трогать (на самом деле, вы не можете изменить порядок вызова). Что касается вашей задачи - вам нужна правильная синхронизация потоков. Как простейшее решение: отмените подписку у наблюдателя перед его удалением.

0 голосов
/ 21 июля 2010

Это типичное гоночное состояние, просто оно выглядит ослепительно.

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

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

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

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

0 голосов
/ 21 июля 2010

Вы не можете изменить порядок уничтожения в наследственных отношениях, и это хорошо (что будет Derived, если его Base будет уничтожено?). Однако, вы можете изменить отношение .

Например, вы всегда можете обернуть Derived в класс (шаблон), который сначала вызывает release() перед уничтожением Derived.

0 голосов
/ 20 июля 2010

Деструктор базового класса всегда вызывается после деструктора производного класса. Вы не должны вызывать какие-либо методы объекта из других потоков после того, как деструктор объекта начнет выполнение (независимо от того, имеет ли он базовый класс или нет). Я бы предложил использовать новый класс, который работает как контейнер для class Base экземпляров и реализует безопасный доступ к Base объектам (вам, вероятно, потребуется использовать один из объектов синхронизации для реализации id).

...