15.7 -- Statyczne funkcje składowe

W poprzedniej lekcji na temat 15.6 -- Statyczne zmienne składowe, dowiedziałeś się, że statyczne zmienne składowe są zmiennymi składowymi należącymi do klasy, a nie obiektami klasy. Jeśli statyczna zmienna składowa jest publiczna, można uzyskać do niej bezpośredni dostęp za pomocą nazwy klasy i operatora rozpoznawania zakresu:

#include <iostream>

class Something
{
public:
    static inline int s_value { 1 };
};

int main()
{
    std::cout << Something::s_value; // s_value is public, we can access it directly
}

A co, jeśli statyczna zmienna składowa jest prywatna? Rozważmy następujący przykład:

#include <iostream>

class Something
{
private: // now private
    static inline int s_value { 1 };
};

int main()
{
    std::cout << Something::s_value; // error: s_value is private and can't be accessed directly outside the class
}

W tym przypadku nie możemy uzyskać dostępu Something::s_value bezpośrednio z main(), ponieważ jest to sprawa prywatna. Zwykle uzyskujemy dostęp do członków prywatnych poprzez funkcje członków publicznych. Chociaż moglibyśmy stworzyć normalną publiczną funkcję członkowską, aby uzyskać dostęp do s_value, musielibyśmy następnie utworzyć instancję obiektu typu klasy, aby móc korzystać z tej funkcji!

#include <iostream>

class Something
{
private:
    static inline int s_value { 1 };

public:
    int getValue() { return s_value; }
};

int main()
{
    Something s{};
    std::cout << s.getValue(); // works, but requires us to instantiate an object to call getValue()
}

Możemy zrobić to lepiej.

Statyczne funkcje składowe

Zmienne składowe nie są jedynym typem składowej, który może być statyczny. Funkcje członkowskie mogą być również statyczne. Oto powyższy przykład z akcesorem statycznej funkcji składowej:

#include <iostream>

class Something
{
private:
    static inline int s_value { 1 };

public:
    static int getValue() { return s_value; } // static member function
};

int main()
{
    std::cout << Something::getValue() << '\n';
}

Ponieważ statyczne funkcje składowe nie są powiązane z konkretnym obiektem, można je wywołać bezpośrednio, używając nazwy klasy i operatora rozpoznawania zakresu (np. Something::getValue()). Podobnie jak statyczne zmienne składowe, można je również wywoływać poprzez obiekty typu klasy, chociaż nie jest to zalecane.

Statyczne funkcje składowe nie mają this wskaźnik

Statyczne funkcje składowe mają dwie interesujące dziwactwa, na które warto zwrócić uwagę. Po pierwsze, ponieważ statyczne funkcje składowe nie są dołączone do obiektu, nie mają this wskaźnika! Ma to sens, jeśli się nad tym zastanowić — wskaźnik this zawsze wskazuje obiekt, nad którym pracuje funkcja składowa. Statyczne funkcje składowe nie działają na obiekcie, więc this wskaźnik nie jest potrzebny.

Po drugie, statyczne funkcje składowe mogą bezpośrednio uzyskiwać dostęp do innych statycznych elementów (zmiennych lub funkcji), ale nie niestatycznych elementów. Dzieje się tak dlatego, że elementy niestatyczne muszą należeć do obiektu klasy, a statyczne funkcje składowe nie mają obiektu klasy, z którym można pracować!

Statyczne elementy członkowskie zdefiniowane poza definicją klasy

Statyczne funkcje składowe można również definiować poza deklaracją klasy. Działa to w taki sam sposób, jak w przypadku zwykłych funkcji składowych.

#include <iostream>

class IDGenerator
{
private:
    static inline int s_nextID { 1 };

public:
     static int getNextID(); // Here's the declaration for a static function
};

// Here's the definition of the static function outside of the class.  Note we don't use the static keyword here.
int IDGenerator::getNextID() { return s_nextID++; } 

int main()
{
    for (int count{ 0 }; count < 5; ++count)
        std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n';

    return 0;
}

Ten program wypisuje:

The next ID is: 1
The next ID is: 2
The next ID is: 3
The next ID is: 4
The next ID is: 5

Zauważ, że ponieważ wszystkie dane i funkcje w tej klasie są statyczne, nie musimy tworzyć instancji obiektu klasy, aby skorzystać z jego funkcjonalności! Ta klasa wykorzystuje statyczną zmienną składową do przechowywania wartości następnego identyfikatora, który ma zostać przypisany, i udostępnia statyczną funkcję składową, która zwraca ten identyfikator i zwiększa go.

Jak zauważono w lekcja 15.2 — Klasy i pliki nagłówkowe, funkcje składowe zdefiniowane w definicji klasy są domyślnie wbudowane. Funkcje składowe zdefiniowane poza definicją klasy nie są domyślnie wbudowane, ale można je wprowadzić za pomocą słowa kluczowego inline . Dlatego należy utworzyć statyczną funkcję składową zdefiniowaną w pliku nagłówkowym inline tak, aby nie naruszyć reguły jednej definicji (ODR), jeśli nagłówek ten zostanie następnie włączony do wielu jednostek tłumaczeniowych.

Ostrzeżenie dotyczące klas ze wszystkimi składowymi statycznymi

Zachowaj ostrożność podczas pisania klas ze wszystkimi składowymi statycznymi. Chociaż takie „czyste klasy statyczne” (zwane także „monostanami”) mogą być przydatne, mają one również pewne potencjalne wady.

Po pierwsze, ponieważ wszystkie statyczne elementy członkowskie są tworzone tylko raz, nie ma możliwości posiadania wielu kopii czystej klasy statycznej (bez klonowania klasy i zmiany jej nazwy). Na przykład, jeśli potrzebne byłyby dwie niezależne IDGenerator, nie byłoby to możliwe w przypadku czystej klasy statycznej.

Po drugie, podczas lekcji o zmiennych globalnych dowiedziałeś się, że zmienne globalne są niebezpieczne, ponieważ dowolny fragment kodu może zmienić wartość zmiennej globalnej i zakończyć się uszkodzeniem innego fragmentu pozornie niepowiązanego kodu. To samo dotyczy klas czysto statycznych. Ponieważ wszyscy członkowie należą do klasy (a nie do obiektu klasy), a deklaracje klas mają zwykle zasięg globalny, czysta klasa statyczna jest w zasadzie odpowiednikiem deklarowania funkcji i zmiennych globalnych w globalnie dostępnej przestrzeni nazw, ze wszystkimi niezbędnymi wadami, jakie mają zmienne globalne.

Zamiast pisać klasę ze wszystkimi statycznymi członkami, rozważ napisanie normalnej klasy i utworzenie jej instancji globalnej (zmienne globalne mają statyczny czas trwania). W ten sposób instancja globalna może być używana, gdy jest to konieczne, ale instancje lokalne nadal mogą być tworzone, jeśli i kiedy jest to przydatne.

Klasy czysto statyczne a przestrzenie nazw

Klasy czysto statyczne w dużym stopniu pokrywają się z przestrzeniami nazw. Obydwa umożliwiają definiowanie zmiennych o statycznym czasie trwania i funkcjach w obrębie ich zakresu. Jednakże jedną znaczącą różnicą jest to, że klasy mają kontrolę dostępu, podczas gdy przestrzenie nazw nie.

Ogólnie rzecz biorąc, klasa statyczna jest lepsza, jeśli masz statyczne elementy danych i/lub potrzebujesz kontroli dostępu. W przeciwnym razie preferuj przestrzeń nazw.

C++ nie obsługuje konstruktorów statycznych

Jeśli możesz inicjować normalne zmienne składowe za pomocą konstruktora, to w związku z rozszerzeniem ma sens, że powinieneś móc inicjować statyczne zmienne składowe za pomocą konstruktora statycznego. I choć niektóre współczesne języki obsługują konstruktory statyczne właśnie w tym celu, C++ niestety nie jest jednym z nich.

Jeśli zmienną statyczną można zainicjować bezpośrednio, żaden konstruktor nie jest potrzebny: statyczną zmienną składową można zainicjować w momencie definicji (nawet jeśli jest ona prywatna). Robimy to w IDGenerator przykładzie powyżej. Oto kolejny przykład:

#include <iostream>

struct Chars
{
    char first{};
    char second{};
    char third{};
    char fourth{};
    char fifth{};
};

struct MyClass
{
	static inline Chars s_mychars { 'a', 'e', 'i', 'o', 'u' }; // initialize static variable at point of definition
};

int main()
{
    std::cout << MyClass::s_mychars.third; // print i

    return 0;
}

Jeśli inicjowanie statycznej zmiennej składowej wymaga wykonania kodu (np. pętli), można to zrobić na wiele różnych, nieco niejasnych sposobów. Jednym ze sposobów, który działa ze wszystkimi zmiennymi, statycznymi lub nie, jest użycie funkcji do utworzenia obiektu, wypełnienia go danymi i zwrócenia go wywołującemu. Tę zwróconą wartość można skopiować do inicjowanego obiektu.

#include <iostream>

struct Chars
{
    char first{};
    char second{};
    char third{};
    char fourth{};
    char fifth{};
};

class MyClass
{
private:
    static Chars generate()
    {
        Chars c{}; // create an object
        c.first = 'a'; // fill it with values however you like
        c.second = 'e';
        c.third = 'i';
        c.fourth = 'o';
        c.fifth = 'u';
        
        return c; // return the object
    }

public:
	static inline Chars s_mychars { generate() }; // copy the returned object into s_mychars
};

int main()
{
    std::cout << MyClass::s_mychars.third; // print i

    return 0;
}

Powiązana treść

Można do tego również użyć lambdy.

Praktyczny przykład tej metodologii pokazujemy na lekcji 8.15 -- Globalne liczby losowe (Random.h) (chociaż robimy to z przestrzenią nazw, a nie klasą statyczną, działa to w ten sam sposób)

Czas quizu

Pytanie nr 1

Przekonwertuj Random przestrzeń nazw w poniższym przykładzie na klasę ze statyczną członkowie:

#include <chrono>
#include <random>
#include <iostream>

namespace Random
{
	inline std::mt19937 generate()
	{
		std::random_device rd{};

		// Create seed_seq with high-res clock and 7 random numbers from std::random_device
		std::seed_seq ss{
			static_cast<std::seed_seq::result_type>(std::chrono::steady_clock::now().time_since_epoch().count()),
				rd(), rd(), rd(), rd(), rd(), rd(), rd() };

		return std::mt19937{ ss };
	}

	inline std::mt19937 mt{ generate() }; // generates a seeded std::mt19937 and copies it into our global object

	// Generate a random int between [min, max] (inclusive)
	inline int get(int min, int max)
	{
		return std::uniform_int_distribution{min, max}(mt);
	}
}

int main()
{
	// Print a bunch of random numbers
	for (int count{ 1 }; count <= 10; ++count)
		std::cout << Random::get(1, 6) << '\t';

	std::cout << '\n';

	return 0;
}

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
231 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze