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


Теперь рассмотрим этот код -

#include <iostream>
#include <string>
#include <unordered_map>

class A{
    std::string id;
  public:
    A(std::string _i): id(_i){}
    virtual void doSomething(){std::cout << "DoSomethingBase\n";}
};

class B : public A{
    std::string name;
  public:
    B(std::string _n):name(_n), A(_n){}
    void doSomething(){std::cout << "DoSomethingDerived\n";}
};

namespace ListA{
    namespace{
        std::unordered_map<std::string, A*> list;   
    }
    void init(){
        list.clear();
    }
    void place(std::string _n, A* a){
        list[_n] = a;
    }
}


int main() {
    ListA::init();
    ListA::place("b1", new B("b1"));
    ListA::place("a1", new A("a1"));
    return 0;
}

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


Что касается предыдущего вопроса, я реорганизовал класс A (базовый класс) и класс B (производные классы) независимо от пространства имен, которое управляет списком этих объектов. Так это хорошая идея или совершенно плохая практика? Есть ли у него недостатки?

Мне была предложена хорошая реализация синглтона:

class EmployeeManager
{
    public:
        static EmployeeManager& getInstance()
        {
            static EmployeeManager    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        EmployeeManager() {};
        std::unordered_map<std::string, Employee&> list;
    public:
        EmployeeManager(EmployeeManager const&) = delete;
        void operator=(const&) = delete;
        void place(const std::string &id, Employee &emp){
            list[id] = emp;
        }
};

class Employee
{
    public:
        virtual void doSomething() = 0;
};

class Writer : public Employee
{
    private: 
        std::string name_;
    public:
        Writer(std::string name) : name_(name) {};
        void doSomething() { };
};

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

4
hg_git 5 Сен 2016 в 20:44

3 ответа

Лучший ответ

это хорошая альтернатива использованию глобальных статических переменных или синглтона?

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

... но зачем вам глобальные переменные (даже в пространствах имен) или синглтоны? В первом примере было бы прекрасно, если бы вместо namespace ListA у вас был struct ListA - плюс удалите этот namespace{. Тогда у вас есть:

int main() {
    ListA list;
    list.init();
    list.place("b1", new B("b1"));
    list.place("a1", new A("a1"));
}

И выглядит нормально.

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

2
marcinj 5 Сен 2016 в 18:06

На ваш вопрос можно ответить без какой-либо строчки кода, поскольку в прошлом на него отвечали многие люди. Синглтоны плохи, потому что ваш код будет зависеть от одного класса и его реализации. Однако вам нужно иметь независимые единицы, которые не знают о реализациях интерфейсов, с которыми они общаются. Распространение значений / ссылки должно (на самом деле это должно выполняться для больших поддерживаемых систем) посредством передачи ссылки от содержащего объекта к его дочернему объекту, системе наблюдателя / событий или шине событий / сообщений. Многие фреймворки используют как минимум два из этих подходов ... Я настоятельно рекомендую придерживаться лучших практик.

0
Florian Salihovic 5 Сен 2016 в 18:20

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

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

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

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

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

Что вы можете сделать для принудительной инициализации, так это создать одну глобальную переменную со всеми зависимыми глобальными объектами внутри в форме struct, которая будет содержать все их как поля ( не статические поля!) . Однако обратите внимание, что точный порядок инициализации будет обеспечиваться только между объектами, являющимися полями этой структуры, но не между ними и любыми другими глобальными объектами.

1
Ethouris 6 Сен 2016 в 13:35