Czy klasy narzędzi ze składnikami statycznymi są anty-wzorcem w C ++?

39

Pytanie Gdzie powinienem umieścić funkcje niezwiązane z klasą wywołało debatę na temat tego, czy w C ++ sens ma łączenie funkcji narzędziowych w klasie, czy po prostu ich istnienie jako wolnych funkcji w przestrzeni nazw.

Pochodzę z tła w języku C #, gdzie ta ostatnia opcja nie istnieje, a zatem naturalnie dąży do używania klas statycznych w małym kodzie C ++, który piszę. Najwyżej głosowana odpowiedź na to pytanie, a także kilka komentarzy mówi jednak, że preferowane są wolne funkcje, sugerując nawet, że klasy statyczne były anty-wzorcem. Dlaczego tak jest w C ++? Przynajmniej na pozór metody statyczne w klasie wydają się nie do odróżnienia od wolnych funkcji w przestrzeni nazw. Skąd więc taka preferencja?

Czy sytuacja wyglądałaby inaczej, gdyby zbiór funkcji narzędziowych potrzebował wspólnych danych, np. Pamięci podręcznej, którą można przechowywać w prywatnym polu statycznym?

PersonalNexus
źródło
Brzmi trochę jak antipattern „funkcjonalny rozkład”.
user281377,
7
Krótka odpowiedź: po prostu nie potrzebujesz klasy, aby zawinąć te funkcje. Darmowe funkcje są znacznie bardziej odpowiednie do twojego zadania niż dzielenie ich na pseudo-konstrukcję OO, co jest obejściem, którego potrzebujesz tylko w językach „czysto-OO”.
Chris mówi Przywróć Monikę

Odpowiedzi:

39

Chyba muszę odpowiedzieć, że powinniśmy porównać intencje obu klas i przestrzeni nazw. Według Wikipedii:

Klasa

W programowaniu obiektowym klasa jest konstrukcją używaną jako plan do tworzenia instancji samej siebie - określanych jako instancje klasy, obiekty klasy, obiekty instancji lub po prostu obiekty. Klasa definiuje elementy składowe, które umożliwiają tym instancjom klasy posiadanie stanu i zachowania. Elementy pola danych (zmienne składowe lub zmienne instancji) umożliwiają obiektowi klasy utrzymanie stanu. Inne rodzaje elementów, zwłaszcza metody, umożliwiają zachowanie obiektu klasy. Instancje klas są typu powiązanej klasy.

Przestrzeń nazw

Zasadniczo przestrzeń nazw jest kontenerem, który zapewnia kontekst dla posiadanych identyfikatorów (nazw, terminów technicznych lub słów) i umożliwia ujednoznacznienie identyfikatorów homonimicznych znajdujących się w różnych przestrzeniach nazw.

Co teraz próbujesz osiągnąć, umieszczając funkcje w klasie (statycznie) lub w przestrzeni nazw? Założę się, że definicja przestrzeni nazw lepiej opisuje twój zamiar - wszystko, czego chcesz, to pojemnik na twoje funkcje. Nie potrzebujesz żadnych funkcji opisanych w definicji klasy. Zauważ, że pierwsze słowa definicji klasy to „ W programowaniu obiektowym ”, ale zbiór funkcji nie ma nic zorientowanego obiektowo.

Prawdopodobnie istnieją też powody techniczne, ale jako że ktoś pochodzący z Javy i stara się omijać język paradygmatu C ++, najbardziej oczywistą odpowiedzią jest dla mnie: Ponieważ nie potrzebujemy OO, aby to osiągnąć.

Gyan alias Gary Buyn
źródło
1
+1 za „nie potrzebujemy OO, aby to osiągnąć”
Ixrec
Zgadzam się z tym, że nie jestem fanem OO, ale wydaje mi się, że jest kilka sytuacji, w których użycie klasy All-static jest jedynym rozwiązaniem, na przykład, zastępując przestrzeń nazw, ponieważ nie można zadeklarować zagnieżdżonej przestrzeni nazw w środku Klasa.
Wael Boutglay
26

Byłbym bardzo ostrożny, nazywając to anty-wzorem. Przestrzenie nazw są zwykle preferowane, ale ponieważ nie ma szablonów przestrzeni nazw, a przestrzeni nazw nie można przekazywać jako parametrów szablonów, używanie klas zawierających wyłącznie elementy statyczne jest dość powszechne.

AProgrammer
źródło
3
Pozostałe odpowiedzi przeszły przez ostatnią linię PO. Przestrzenie nazw są nazwanymi zakresami, więc jeśli potrzebujesz kontroli dostępu, klasy są jedynym dostępnym narzędziem.
cmannett85
2
@ cbamber85, żeby być uczciwym, umieściłem tę linię dopiero po przeczytaniu dwóch pierwszych odpowiedzi.
PersonalNexus
@PersonalNexus Ah, wystarczy, nie zdawałem sobie sprawy!
cmannett85
10
@ cbamber85: To nie do końca prawda. Osiągnąłbyś jeszcze lepszą enkapsulację poprzez umieszczenie „prywatnych” funkcji w pliku implementacji, aby użytkownicy nie widzieli nawet prywatnej deklaracji.
UncleBens,
2
Szablony +1 są rzeczywiście punktem, w którym w przestrzeni nazw wciąż brakuje funkcji.
Chris mówi Przywróć Monikę
13

Wcześniej musiałem wziąć udział w zajęciach FORTRAN. Do tego czasu znałem inne imperatywne języki, więc pomyślałem, że mogę robić FORTRAN bez większego uczenia się. Kiedy oddałem swoją pierwszą pracę domową, profesor zwrócił mi ją i poprosił o jej powtórzenie: powiedział, że programy Pascala napisane w składni FORTRAN nie liczą się jako prawidłowe zgłoszenia.

Podobny problem występuje tutaj: używanie klas statycznych do hostowania funkcji narzędziowych w C ++ jest obcym idiomem dla C ++.

Jak wspomniałeś w swoim pytaniu, użycie klas statycznych dla funkcji narzędziowych w C # jest koniecznością: funkcje wolnostojące po prostu nie są opcją w C #. Język potrzebny do opracowania wzorca umożliwiającego programistom definiowanie funkcji wolnostojących w inny sposób - mianowicie w ramach statycznych klas użyteczności. To była sztuczka Java wykorzystana słowo w słowo: na przykład java.lang.Math i System.Math .NET są prawie izomorficzne.

Jednak C ++ oferuje przestrzenie nazw, inne narzędzie do osiągnięcia tego samego celu i aktywnie wykorzystuje je w implementacji swojej standardowej biblioteki. Dodanie dodatkowej warstwy klas statycznych jest nie tylko niepotrzebne, ale także nieco sprzeczne z intuicją dla czytelników bez tła w języku C # lub Java. W pewnym sensie wprowadzasz „tłumaczenie pożyczki” na język dla czegoś, co można wyrazić natywnie.

Kiedy twoje funkcje muszą współdzielić dane, sytuacja jest inna. Ponieważ twoje funkcje nie są już ze sobą powiązane , wzorzec Singleton staje się preferowanym sposobem spełnienia tego wymagania.

dasblinkenlight
źródło
6
+1, z wyjątkiem polecania singletonów. Są one nie preferowane w C ++! Funkcje mogą być w klasie, jeśli muszą dzielić stan, ale klasy nie powinny być singletonami, chyba że naprawdę, naprawdę muszą. I zwykle nie.
Maks.
@ Maxpm Nie zrozum mnie źle, singleton jest preferowany tylko w stosunku do globalnych zmiennych statycznych. Poza tym nie ma w tym nic „preferowanego”.
dasblinkenlight
2
Jest taki dobry artykuł Singletons: Rozwiązywanie problemów, o których nie wiedziałeś, że nigdy nie miałeś od 1995 roku . Podsumowując, mówi, że połączenie dobrze znanej instancji z samą klasą niepotrzebnie ogranicza twoją elastyczność i nie daje ci nic. Przez większość czasu po prostu mają zajęcia i mają gdzieś wspólną instancję, ale nie róbcie z tego singletonu.
Jan Hudec
Nie jestem pewien, czy „obcy idiom” jest właściwym terminem. To bardzo powszechny idiom. Myślę, że sporo zespołów programistycznych używa e2 Stroustrup ...
Nick Keighley,
10

Być może musisz zapytać, dlaczego chcesz mieć klasę statyczną?

Jedynym powodem, dla którego mogę wymyślić, jest to, że inne języki (Java i C #), które są bardzo „wszystko jest klasą”, wymagają ich. Te języki w ogóle nie mogą tworzyć funkcji najwyższego poziomu, więc wymyślili sztuczkę, aby je zachować, i to była statyczna funkcja członka. To trochę obejście, ale C ++ tego nie potrzebuje, możesz bezpośrednio tworzyć zupełnie nowe, niezależne funkcje najwyższego poziomu.

Jeśli potrzebujesz funkcji, które działają na określonym elemencie danych, warto połączyć je w klasę, która przechowuje dane, ale wtedy przestają one być funkcjami i zaczynają być członkami tej klasy, które działają na danych klasy.

Jeśli masz funkcję, która nie działa na określonym typie danych (używam tego słowa, ponieważ klasy są sposobami do definiowania nowych typów danych), to naprawdę jest anty-wzorzec, aby umieścić je w klasie, nie inaczej niż cele semantyczne.

gbjbaanb
źródło
10
Rzeczywiście - powiedziałbym, że „klasa ze wszystkimi członami statycznymi” w Javie jest w rzeczywistości próbą obejścia ograniczenia Javy.
Charles Salvia
@CharlesSalvia: Byłem uprzejmy :) Mam takie samo złe przeczucie, że main () również należy do klasy java, choć rozumiem, dlaczego to zrobili.
gbjbaanb
To wydaje się stanowić odpowiedź na pytanie, dlaczego chcesz mieć klasę całkowicie statyczną. A może źle cię rozumiem? Na przykład muszę trzymać zmienną, której wartość może się zmienić, która jest odczytywana przez jedną klasę (którą zamierzam przechowywać w pamięci ROM) i zapisywana przez inną klasę (która będzie w pamięci RAM). Samo umieszczenie w znanym miejscu pamięci RAM nie jest wystarczające - zawinięcie go (i jego akcesoriów) w klasę pozwala mi dostroić kontrolę dostępu. Prawie to, co opisujesz w swoim drugim akapicie.
iheanyi
5

Przynajmniej na pozór metody statyczne w klasie wydają się nie do odróżnienia od wolnych funkcji w przestrzeni nazw.

Innymi słowy posiadanie klasy zamiast przestrzeni nazw nie ma zalet.

Skąd więc taka preferencja?

Z jednej strony oszczędza ci ciągłego pisania na klawiaturze static, choć jest to prawdopodobnie niewielka korzyść.

Główną zaletą jest to, że jest to najmniej wydajne narzędzie do pracy. Klasy mogą być używane do tworzenia obiektów, mogą być używane jako typ zmiennych lub jako argumenty szablonu. Żadna z tych funkcji nie jest pożądana dla twojej kolekcji funkcji. Dlatego lepiej jest użyć narzędzia, które nie ma tych funkcji, aby klasa nie mogła zostać przypadkowo wykorzystana.

Wynika z tego, że użycie przestrzeni nazw natychmiast pokazuje wszystkim użytkownikom kodu, że jest to zbiór funkcji, a nie plan tworzenia obiektów.

sepp2k
źródło
5

... kilka komentarzy mówi jednak, że preferowane są funkcje bezpłatne, sugerując nawet, że klasy statyczne były anty-wzorcem. Dlaczego tak jest w C ++? Przynajmniej na pozór metody statyczne w klasie wydają się nie do odróżnienia od wolnych funkcji w przestrzeni nazw. Skąd więc taka preferencja?

Klasa statyczna wykona zadanie, ale to tak, jakby jechać 53-metrową półciężarówką do sklepu spożywczego po frytki i salsę, gdy zrobi to czterodrzwiowy sedan (tj. Przesada). Zajęcia mają niewielki dodatkowy koszt, a ich istnienie może sprawić wrażenie, że zainicjowanie jednego może być dobrym pomysłem. Bezpłatne funkcje oferowane przez C ++ (i C, gdzie wszystkie funkcje są bezpłatne) tego nie robią; są tylko funkcjami i niczym więcej.

Czy sytuacja wyglądałaby inaczej, gdyby zbiór funkcji narzędziowych potrzebował wspólnych danych, np. Pamięci podręcznej, którą można przechowywać w prywatnym polu statycznym?

Nie całkiem. Pierwotnym celem staticw C (a później C ++) było oferowanie trwałej pamięci masowej, która może być użyta na dowolnym poziomie zakresu:

int counter() {
    static int value = 0;
    value++;
}

Możesz przenieść zakres na poziom pliku, dzięki czemu będzie widoczny dla wszystkich węższych zakresów, ale nie poza plikiem:

static int value = 0;

int up() { value++; }
int down() { value--; }

Prywatny członek klasy statycznej w C ++ służy temu samemu celowi, ale jest ograniczony do zakresu klasy. Technika zastosowana w counter()powyższym przykładzie działa również wewnątrz metod C ++ i jest to coś, co naprawdę poleciłbym zrobić, jeśli zmienna nie musi być widoczna dla całej klasy.

Blrfl
źródło
Przypuszczam, że grupa niepowiązanych funkcji narzędziowych, które nie współużytkują danych, powinna być lepiej pogrupowana w przestrzeni nazw. Ale jeśli masz grupę ściśle powiązanych funkcji narzędziowych, które potrzebują dostępu do współdzielonych danych i być może kontroli dostępu, klasa statyczna jest najlepszym wyborem. Statyczne w obrębie funkcji nie jest ponownie wysyłane. Używanie globalnego modułu ... nieco lepiej w tym kontekście, ale nadal uważam, że klasa statyczna jest najlepszym rozwiązaniem, jeśli masz opisane wymagania.
iheanyi
Jeśli dzielą się danymi, czy mogą być lepsi jako klasa bez metod statycznych?
Nick Keighley,
@NickKeighley To zależy od tego, kto wygra debatę, czy lepiej jest utworzyć jedną instancję i przekazać ją wszystkim, którzy jej potrzebują, czy po prostu sprawić, by była statyczna w swojej klasie.
Blrfl,
1

Jeśli funkcja nie utrzymuje żadnego stanu i jest ponownie wprowadzana, wydaje się, że nie ma większego sensu wsuwanie jej do klasy (chyba że jest to wymagane przez język). Jeśli funkcja zachowuje pewien stan (np. Można ją zabezpieczyć tylko przed wątkami za pomocą statycznego muteksu), wówczas metody statyczne w klasie wydają się odpowiednie.

Martin James
źródło
1

Znaczna część dyskusji na ten temat tutaj sens, choć nie jest czymś fundamentalnym o C ++, który sprawia, że namespacesi classes / structs bardzo różne.

Klasy statyczne (klasy, w których wszyscy członkowie są statyczni, a klasa nigdy nie zostanie utworzona), same są obiektami. Nie zawierają one po prostu namespacefunkcji.

Szablon meta-programowania pozwala nam używać klasy statycznej jako obiektu do kompilacji.

Rozważ to:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Aby go użyć, potrzebujemy funkcji zawartych w klasie. Przestrzeń nazw nie będzie tu działać. Rozważać:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Teraz go użyć:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Tak długo, jak nowy alokator udostępnia kompatybilną funkcję allocatei release, przejście do nowego alokatora jest łatwe.

Nie można tego osiągnąć za pomocą przestrzeni nazw.

Czy zawsze potrzebujesz funkcji, aby być częścią klasy? Nie.

Czy używanie klasy statycznej jest anty-wzorcem? To zależy od kontekstu.

Czy sytuacja wyglądałaby inaczej, gdyby zbiór funkcji narzędziowych potrzebował wspólnych danych, np. Pamięci podręcznej, którą można przechowywać w prywatnym polu statycznym?

W takim przypadku to, co próbujesz osiągnąć, prawdopodobnie najlepiej będzie osiągnąć poprzez programowanie obiektowe.

Michael Gazonda
źródło
Użycie słowa kluczowego inline jest tutaj zbędne, ponieważ metody zdefiniowane w definicji klasy są domyślnie wbudowane. Zobacz en.cppreference.com/w/cpp/language/inline .
Trevor
1

Jak słynie John Carmack:

„Czasami elegancka implementacja jest tylko funkcją. Nie metodą. Nie klasą. Nie strukturą. Po prostu funkcją.”

Myślę, że to podsumowuje. Dlaczego miałbyś uczynić z niej klasę, skoro wyraźnie nie jest klasą? C ++ ma luksus, że faktycznie można korzystać z funkcji; w Javie wszystko jest metodą. Potrzebujesz klas użytkowych i wielu z nich.

juhist
źródło