Синглтон с приватным деструктором с использованием std::unique_ptr

Я создал все синглтоны в своей программе с учетом этого документа: http://erdani.com/publications/DDJ_Jul_Aug_2004_revised.pdf (если кому-то интересно, почему синглтон, все они являются фабриками, и некоторые из них хранят некоторые глобальные настройки, касающиеся того, как они должны создавать экземпляры).

Каждый из них выглядит примерно так:

декларация:

class SingletonAndFactory {
    static SingletonAndFactory* volatile instance;

public:
    static SingletonAndFactory& getInstance();

private:
    SingletonAndFactory();

    SingletonAndFactory(
        const SingletonAndFactory& ingletonFactory
    );

    ~SingletonAndFactory();
};

определение:

boost::mutex singletonAndFactoryMutex;

////////////////////////////////////////////////////////////////////////////////

// class SingletonAndFactory {

SingletonAndFactory* volatile singletonAndFactory::instance = 0;

// public:

SingletonAndFactory& SingletonAndFactory::getInstance() {
    // Singleton implemented according to:
    // "C++ and the Perils of Double-Checked Locking".
    if (!instance) {
        boost::mutex::scoped_lock lock(SingletonAndFactoryMutex);
        if (!instance) {
            SingletonAndFactory* volatile tmp = (SingletonAndFactory*) malloc(sizeof(SingletonAndFactory));
            new (tmp) SingletonAndFactory; // placement new
            instance = tmp;
        }
    }
    return *instance;
}

// private:

SingletonAndFactory::SingletonAndFactory() {}

// };

Оставив в стороне вопрос какой дизайн синглтона лучше (поскольку это приведет к бессмысленной флеймовой войне), мой вопрос: принесет ли мне пользу замена обычного указателя на std::unique_ptr? В частности, вызовет ли он деструктор синглтона при выходе из программы? Если да, то как бы я этого добился? Когда я попытался добавить что-то вроде friend class std::unique_ptr<SingletonAndFactory>;, это не сработало, поскольку компилятор продолжает жаловаться, что деструктор является закрытым.

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


person Mateusz Kubuszok    schedule 09.10.2013    source источник
comment
Thing& getThing() { static Thing thing; return thing; } Забудьте о блокировках, забудьте об одиночках.   -  person R. Martinho Fernandes    schedule 09.10.2013
comment
И... серьезно? malloc + место размещения новое? О, давай...   -  person R. Martinho Fernandes    schedule 09.10.2013
comment
@R.MartinhoFernandes: Если вы собираетесь использовать синглтоны, вы можете сделать их как можно более неправильными. Есть те, кто выступает за заставлять неверный код выглядеть неправильно.   -  person Mike Seymour    schedule 09.10.2013
comment
Мне также интересно, используется ли указатель для ввода-вывода с отображением памяти, потому что он помечен volatile. C++11 не присваивает volatile семантики, связанной с потоком.   -  person R. Martinho Fernandes    schedule 09.10.2013
comment
Похоже, что volatile был скопирован из примера в связанном документе, описывающего, как volatile недостаточно для обеспечения безопасности потоков. Вам нужен C++11, чтобы обеспечить необходимые гарантии атомарности; и как только у вас есть С++ 11, вы можете также использовать локальную статику.   -  person Mike Seymour    schedule 09.10.2013
comment
Я придумал какое-то решение, когда я еще не использовал флаг -std=c++11, и я знал о C++ гораздо меньше, чем сейчас (хотя это все еще немного). Если С++ 11, наконец, даст некоторую атомарность, я мог бы заменить весь malloc простым конструктором, но это всего лишь вопрос реализации одной функции. Вот почему я не спрашивал о способе реализации синглтона - пока API не меняется и тесты проходят, я могу переключить реализацию за ними на что-то лучшее в любое время, когда захочу. Хотя спасибо за информацию об этой конкретной функции C++11. Это будет полезно.   -  person Mateusz Kubuszok    schedule 09.10.2013
comment
@maddening: безумие malloc здесь ничего не делает для обеспечения безопасности потоков. И instance = new Thing;, и ваша чепуха включают в себя одно присваивание instance и, следовательно, так же безопасны (или, в данном случае, небезопасны), как и друг друга.   -  person Mike Seymour    schedule 09.10.2013
comment
@MikeSeymour Ну, СЕЙЧАС я знаю, что malloc и новое размещение бессмысленны, но я не знал этого в тот момент, когда писал этот конкретный фрагмент кода. Только недавно я начал немного лучше понимать C++, я начал использовать флаги -std=C++11 -Wall -Wextra -pedantic и, например. изменил BOOST_FOREACH на C++11 для. Тело getInstance было написано, когда я буквально начинал свое приключение с C++, имея в основном опыт работы с Java SE. Потребовалось несколько недель кодирования и чтения, чтобы понять некоторые вещи. Поэтому недавно я переписываю некоторые части своего кода, чтобы сделать их лучше. Я также разберусь с malloc.   -  person Mateusz Kubuszok    schedule 09.10.2013


Ответы (2)


Удаление выполняет не сам unique_ptr, а средство удаления. Итак, если вы хотите использовать подход friend, вам нужно будет сделать это:

friend std::unique_ptr<SingletonFactory>::deleter_type;

Однако я не думаю, что гарантировано, что средство удаления по умолчанию не делегирует фактическое delete другой функции, что нарушит это.

Вместо этого вы можете указать свой собственный детерминатор, например, вот так:

class SingletonFactory {
    static std::unique_ptr<SingletonFactory, void (*)(SingletonFactory*)> volatile instance;

public:
    static SingletonFactory& getInstance();

private:
    SingletonFactory();

    SingletonFactory(
        const SingletonFactory& ingletonFactory
    );

    ~SingletonFactory();

    void deleter(SingletonFactory *d) { d->~SingletonFactory(); free(d); }
};

И в функции создания:

SingletonFactory* volatile tmp = (SingletonFactory*) malloc(sizeof(SingletonFactory));
new (tmp) SingletonFactory; // placement new
instance = decltype(instance)(tmp, &deleter);
person Angew is no longer proud of SO    schedule 09.10.2013
comment
Спасибо! Это ответ, который я искал. Я знаю, что есть несколько способов добиться одноэлементного поведения (например, silviuardelean.ro/ 2012/06/05/few-singleton-approaches), и разница между некоторыми из них только во вкусе. Я искал способ улучшить свою собственную реализацию. Потроха getInstance() можно легко переключить на что-то другое, и я, вероятно, сделаю это в ближайшее время (поскольку я перешел на C++11), но я хотел улучшить свой дизайн в целом. - person Mateusz Kubuszok; 09.10.2013
comment
@maddening Я не включил это в ответ (потому что у меня не было времени прочитать ссылку в вашем вопросе), но, пожалуйста, обратите пристальное внимание на комментарии, которые другие сделали на ваш вопрос. Многие дизайнерские решения в вашем коде (размещение new, volatile) в лучшем случае сомнительны. - person Angew is no longer proud of SO; 09.10.2013
comment
Я хорошо знаю об этом. В настоящее время я нахожусь в процессе выяснения того, как улучшить код в моем проекте. На самом деле это скорее проект для самообучения, где я могу экспериментировать, чтобы лучше понять C++. Я обязательно воспользуюсь некоторыми из этих советов, хотя на данный момент синглтоны останутся. - person Mateusz Kubuszok; 09.10.2013

В C++11 вы можете гарантировать потокобезопасную ленивую инициализацию и уничтожение в конце программы, используя локальный статический код:

SingletonAndFactory& SingletonAndFactory::getInstance() {
    static SingletonAndFactory instance;
    return instance;
}

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

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

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

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

person Mike Seymour    schedule 09.10.2013