Недавно я разместил вопрос о 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() { };
};
Честно говоря, я никогда не пробовал одноэлементный шаблон и избегаю использовать его напрямую, так как у меня нет предыдущего опыта, и я бы предпочел сначала использовать его в своих домашних проектах.
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
в вашей основной функции, если вам нужно использовать ее в каком-то другом классе, затем передайте ее по ссылке или указателю.
На ваш вопрос можно ответить без какой-либо строчки кода, поскольку в прошлом на него отвечали многие люди. Синглтоны плохи, потому что ваш код будет зависеть от одного класса и его реализации. Однако вам нужно иметь независимые единицы, которые не знают о реализациях интерфейсов, с которыми они общаются. Распространение значений / ссылки должно (на самом деле это должно выполняться для больших поддерживаемых систем) посредством передачи ссылки от содержащего объекта к его дочернему объекту, системе наблюдателя / событий или шине событий / сообщений. Многие фреймворки используют как минимум два из этих подходов ... Я настоятельно рекомендую придерживаться лучших практик.
Я не уверен, что вы это уже знаете, но вам нужно помнить, что Singleton действительно является глобальной переменной с отложенной инициализацией .
Ленивая инициализация - это инструмент для решения проблемы инициализации объекта всегда в то время, когда вы действительно хотите его использовать - будь то для какой-то реальной программной функции или инициализации другого зависимого объекта. Это сделано, чтобы отложить инициализацию до первого момента использования объекта.
Статический объект просто инициализируется в тот момент, когда он сначала возникает необходимость в создании - однако, когда этот момент действительно есть, не определен, по крайней мере, в C ++.
Вы можете заменить ленивую инициализацию статической, но вы должны каким-то образом гарантировать, что инициализация происходит в определенном порядке.
Определение переменных внутри пространства имен - это не что иное, как объявление переменных глобально. Пространства имен открыты, правила внутри пространства имен такие же, как и вне пространства имен, за исключением разрешения символов.
Что вы можете сделать для принудительной инициализации, так это создать одну глобальную переменную со всеми зависимыми глобальными объектами внутри в форме struct
, которая будет содержать все их как поля ( не статические поля!) . Однако обратите внимание, что точный порядок инициализации будет обеспечиваться только между объектами, являющимися полями этой структуры, но не между ними и любыми другими глобальными объектами.
Похожие вопросы
Связанные вопросы
Новые вопросы
c++
C++ — это язык программирования общего назначения. Изначально он разрабатывался как расширение C и имел аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде, который будет скомпилирован с помощью компилятора C++. Используйте тег версии для вопросов, связанных с конкретной стандартной версией [C++11], [C++14], [C++17], [C++20] или [C++23]. и т.д.