Вынуждая что-то уничтожить последним в C ++ - PullRequest
6 голосов
/ 13 ноября 2008

Я работаю над приложением C ++, в котором есть некоторые объекты контроллера, которые регулярно создаются и уничтожаются (используя new). Необходимо, чтобы эти контроллеры зарегистрировались в другом объекте (назовем его controllerSupervisor) и отменили регистрацию при разрушении.

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

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

Будем благодарны за любые подсказки / предложения о возможных вариантах.

Ответы [ 15 ]

5 голосов
/ 14 ноября 2008

В «Современном C ++ Design» Александреску есть целая глава на эту тему (Глава 6, Singletons). Он определяет одноэлементный класс, который может управлять зависимостями, даже между самими синглетонами.

Кстати, вся книга очень рекомендуется.

5 голосов
/ 13 ноября 2008

Порядок уничтожения автоматических переменных (которые включают в себя «нормальные» локальные переменные, которые вы используете в функциях) находится в обратном порядке их создания. Поэтому поместите контроллер SuperSvisor наверх.

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

Я думаю, вам стоит подумать, как это рекомендовал Майк:

  1. Создание выполняется с использованием одноэлементного шаблона (поскольку порядок инициализации объектов в разных единицах перевода не определен) при первом использовании путем возврата указателя на объект-функцию супервизора.
  2. Супервизор обычно уничтожается (используя правила уничтожения статики в функциях). Дерегистрация контроллеров с использованием статической функции супервизора. Тот проверяет, уничтожен ли уже супервизор (проверяя указатель на != 0). Если это так, то ничего не делается. В противном случае руководитель уведомляется.

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

2 голосов
/ 13 ноября 2008

Вы можете использовать шаблон Observer. Контроллер сообщает своему руководителю, что он уничтожается. И Супервайзер сообщает то же самое своему ребенку после уничтожения.

Взгляните на http://en.wikipedia.org/wiki/Observer_pattern

1 голос
/ 14 ноября 2008

GNU gcc / g ++ предоставляет непереносимые атрибуты для типов, которые очень полезны. Одним из этих атрибутов является init_priority , который определяет порядок, в котором создаются глобальные объекты, и, как следствие, обратный порядок их разрушения. От мужчины:

init_priority (PRIORITY)

 In Standard C++, objects defined at namespace scope are guaranteed
 to be initialized in an order in strict accordance with that of
 their definitions _in a given translation unit_.  No guarantee is
 made for initializations across translation units.  However, GNU
 C++ allows users to control the order of initialization of objects
 defined at namespace scope with the init_priority attribute by
 specifying a relative PRIORITY, a constant integral expression
 currently bounded between 101 and 65535 inclusive.  Lower numbers
 indicate a higher priority.

 In the following example, `A' would normally be created before
 `B', but the `init_priority' attribute has reversed that order:

      Some_Class  A  __attribute__ ((init_priority (2000)));
      Some_Class  B  __attribute__ ((init_priority (543)));

 Note that the particular values of PRIORITY do not matter; only
 their relative ordering.
1 голос
/ 14 ноября 2008

Пара предложений:

  • сделать controllerSupervisor одноэлементным (или обернуть его в одноэлементный объект, который вы создаете для этой цели), доступ к которому осуществляется через статический метод, который возвращает указатель, тогда dtors зарегистрированных объектов могут вызывать статический метод доступа (который в случае закрытия приложения и уничтожения controllerSupervisor возвращает NULL), и в этом случае эти объекты могут избежать вызова метода отмены регистрации.

  • создайте controllerSupervisor в куче, используя new, и используйте что-то вроде boost::shared_ptr<> для управления его временем жизни. Раздайте shared_ptr<> в методе статического доступа синглтона.

0 голосов
/ 19 ноября 2008

Когда я прочитал заголовок к этому вопросу, я сразу же спросил себя: «Если бы какой-либо объект мог гарантировать, что он был уничтожен (разрушен?) Последним, то что произойдет, если два объекта примут этот метод?»

0 голосов
/ 14 ноября 2008

Стандарт C ++ определяет порядок инициализации / уничтожения, когда все рассматриваемые переменные помещаются в один файл («единица перевода»). Все, что охватывает более одного файла, становится непереносимым.

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


Можно сделать этот поток безопасным, даже если контроллер необходимо уничтожить до конца программы (то есть, если контроллеры могут быть недолговечны), тогда они (или кто-то еще).

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

Во-вторых, если вас беспокоит это состояние гонки, вы можете исправить его, скажем, требуя, чтобы все запросы на уничтожение проходили через Supervisor. Я хочу уничтожить себя. Я либо говорю Супервайзеру, чтобы он сказал мне, либо я регистрирую это намерение у Супервайзера. Если кто-то еще, включая Супервайзера, хочет, чтобы я был уничтожен, он делает это через Супервизора.

0 голосов
/ 14 ноября 2008

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

Добавьте WaitForMultipleObjects в деструктор Supervisor, который будет ожидать уничтожения всех контроллеров.

В деструкторе контроллеров вы можете вызвать событие Exit контроллера.

Вам необходимо поддерживать глобальный массив дескрипторов событий Exit для каждого контроллера.

0 голосов
/ 14 ноября 2008

Хотя уродливо, это может быть самый простой метод:

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

Другие указали на лучший дизайн, но этот прост. (и некрасиво)

Я также предпочитаю схему наблюдателя в этом случае.

0 голосов
/ 14 ноября 2008

Хорошо, как предлагается в другом месте, сделать супервизор одноэлементным (или подобным образом контролируемым объектом, то есть областью действия для сеанса).

При необходимости используйте соответствующих охранников (ведущие и т. Д.) Вокруг синглтона.

// -- client code --
class ControllerClient {
public:
    ControllerClient() : 
        controller_(NULL)
        {
            controller_ = Controller::create();
        }

    ~ControllerClient() {
        delete controller_;
    }
    Controller* controller_;
};

// -- library code --
class Supervisor {
public: 
    static Supervisor& getIt() {        
        if (!theSupervisor ) {
            theSupervisor = Supervisor();
        }
        return *theSupervisor;
    } 

    void deregister(Controller& controller) {
        remove( controller );
        if( controllers_.empty() ) {
            theSupervisor = NULL;
            delete this;
        }       
    }

private:    
    Supervisor() {} 

    vector<Controller*> controllers_;

    static Supervisor* theSupervisor;
};

class Controller {
public: 
    static Controller* create() {
        return new Controller(Supervisor::getIt()); 
    } 

    ~Controller() {
        supervisor_->deregister(*this);
        supervisor_ = NULL;
    }
private:    
    Controller(Supervisor& supervisor) : 
        supervisor_(&supervisor)
        {}
}
...