2016-09-05 45 views
5

Niedawno opublikowałem pytanie na SO dotyczące użycia klasy, która nosi nieco oddzielnej funkcjonalności, która powinna, idealnie. Polecono mi zapoznać się z pojedynczym wzorcem, aby utworzyć tylko jedną instancję klasy i zarządzać zbiorem operacji obracających się wokół danych, które on obejmuje. Tutaj możesz zobaczyć pytanie - using Static Container for base and derived classes.używanie przestrzeni nazw zamiast pojedynczych


Rozważmy teraz ten kod -

#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; 
} 

ignorując fakt, że nadal jestem przy użyciu surowych wskaźników, które są nieszczelne pamięci, jeśli program nie kończy, ponieważ jest, jest to dobry alternatywa do korzystania z globalnych zmiennych statycznych, czy singleton?


W odniesieniu do poprzedniego pytania, mam reorganizacji klasy A (klasa bazowa) i klasy B (klasy pochodne), niezależnie od przestrzeni nazw, która zarządza listę tych obiektów. Czy to dobry pomysł, czy też całkowicie zła praktyka? Czy są jakieś niedociągnięcia?

Dobrym Singleton realizacja I Zasugerowano była następująca -

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() { }; 
}; 

Szczerze mówiąc nigdy nie próbowałem Singleton i mam shying go używać bezpośrednio, ponieważ nie mam wcześniejszego doświadczenia i wolałbym go najpierw użyć w moich domowych projektach.

+6

Twoja reakcja na kogoś polecającego wzór singletonu powinna być taka, kiedy spotkałeś kogoś, kto zabiera cię do zadymionego pokoju i po kilku rundach pokera sugeruje, że "powinniśmy sprawić, by było ciekawie". –

+2

Wzorzec singleton jest sposobem na * wymuszenie * utworzenia tylko jednej instancji. Czy czujesz potrzebę tworzenia wielu instancji, gdy naprawdę potrzebujesz tylko jednego? Jeśli nie, po prostu stwórz potrzebny przedmiot i skończ go! –

+0

@ BoPersson czy naprawdę muszę utworzyć obiekt dla mojego przypadku użycia? Czy nie potrzebuję po prostu mapy std :: map z powiązanymi z nią specjalnymi funkcjami? –

Odpowiedz

2

jest to dobra alternatywa dla używania globalnych zmiennych statycznych, czy singleton?

Nie, ponieważ możesz napotkać inny problem: static initialization order fiasco. Są sposoby, aby to naprawić - ale z funkcjami ze zmiennymi statycznymi - które wyglądają jak single.

... ale dlaczego potrzebujesz zmiennych globalnych (nawet w przestrzeniach nazw) lub pojedynczych? W twoim pierwszym przykładzie byłoby zupełnie dobrze, gdybyś zamiast namespace ListA miałeś struct ListA - plus usunąć to namespace{. Następnie masz:

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

i wygląda dobrze.

Następnie ponownie podchodź do singletonu - bez potrzeby - utwórz zmienną typu EmployeeManager w swojej głównej funkcji, jeśli potrzebujesz jej użyć w innej klasie, a następnie przekaż ją za pomocą odnośnika lub wskaźnika.

+0

Nie wiem, wszyscy kazali mi mieć tylko jeden obiekt na maksa. Prawdopodobnie dlatego, że może to prowadzić do błędów po zadeklarowaniu dwóch lub więcej? tbh idk:/ –

+0

nadal muszę zapytać, w moim przykładzie, czy mam tradycyjne pliki, takie jak ah & a.cpp, podobnie dla bh i b.cpp, a następnie inny ListA.cpp z przestrzenią nazw ListA i użyć powyższego kodu, jak "zdarza się, że fiasko zamówienia na stałe podczas inicjalizacji"? Są niezależne od siebie, a ListA.cpp udostępnia tylko niektóre funkcje i zmienną do działania po:/ –

+0

Problem będzie wtedy, gdy spróbujesz uzyskać dostęp (na przykład wewnątrz konstruktora) do globalnych instancji klasy przed głównym zostaje wykonany. Twój przykładowy kod nie cierpi z tego powodu. – marcinj

0

Na twoje pytanie można odpowiedzieć bez żadnego wiersza kodu, ponieważ wiele osób odpowiedziało w przeszłości. Singletony są złe, ponieważ twój kod będzie zależał od jednej klasy i jej implementacji. To, czego chcesz, to mieć niezależne jednostki, które nie wiedzą o implementacjach interfejsów, z którymi rozmawiają. Propagacja wartości/referencji powinna (w rzeczywistości musi być wykonana dla dużych systemów utrzymywalnych) poprzez odniesienie przechodzące od zawierania obiektu do jego potomka, systemu obserwatora/zdarzenia lub szyny zdarzeń/komunikatów. Wiele frameworków używa w dwóch z tych podejść ... Gorąco polecam trzymać się najlepszych praktyk.

+2

Czy możesz rozwinąć nieco więcej - "Propagacja wartości/referencji powinna (w rzeczywistości musi być wykonana dla dużych systemów utrzymywalnych) poprzez odniesienie przechodzące od zawierania obiektu do jego dziecka, systemu obserwatora/zdarzenia lub magistrali zdarzeń/komunikatów . " Krótkie przykłady dobrze by mi poszły. –

1

Nie jestem pewien, czy wiesz już, ale trzeba pamiętać, że Singleton naprawdę globalna zmienna z leniwej inicjalizacji.

Lazy inicjalizacja to narzędzie, które rozwiązuje problem inicjalizacji obiektu zawsze w czasie, gdy naprawdę chce się go użyć - czy to dla jakiejś funkcji programu rzeczywistego, czy też inicjowania innego, zależnego obiektu. Odbywa się to w celu opóźnienia inicjalizacji do momentu pierwszego użycia obiektu.

Obiekt statyczny jest po prostu inicjowany w momencie, kiedy najpierw musi powstać - jednak w tym momencie jest niezdefiniowany, przynajmniej w C++.

Możesz zamienić leniwą inicjalizację na statyczną inicjalizację, ale musisz się upewnić, że inicjalizacja odbywa się w określonej kolejności.

Definiowanie zmiennych w przestrzeni nazw to nic innego jak deklarowanie zmiennych globalnie. Przestrzenie nazw są otwarte, reguły wewnątrz przestrzeni nazw są takie same jak poza przestrzenią nazw, z wyjątkiem rozdzielczości symbolu.

Co można zrobić, aby wymusić uporządkowaną inicjalizacji jest stworzenie jednej zmiennej globalnej ze wszystkimi zależnymi globalnych obiektów wewnątrz, w postaci struct który będzie zawierał wszystkie je jako pola (nie pól statycznych!). Pamiętaj jednak, że dokładna kolejność inicjowania będzie zapewniona tylko między obiektami będącymi polami tej struktury, a nie między nimi a innymi obiektami globalnymi.