Zwykle programuję w C (a właściwie w jego rozszerzeniu C+), ale zastanawiam się nad obiektowością języka C++.
Wygodnie było by mieć możliwość definiowania obiektów (w C++ nazywanych klasami lub strukturami) w blokach strukturalnych programu, tak by były zwalniane przez kompilator automatycznie po opuszczeniu bloku strukturalnego. Jednak ze względu na sposób, w jaki kompilator obsługuje w programie wynikowym te obiekty, skłaniam się do ich nieużywania. Mianowicie są dwa problemy:
- Obiekty są umieszczane na stosie, co zaburza optymalizację rozmiaru stosu, jego ograniczenia do zmiennych tymczasowych, adresów powrotu z procedur — i przewidywalności.
- Obiekty są zwalniane przy opuszczaniu bloku strukturalnego programu, co jest naturalne w programie jednozadaniowym (jednowątkowym), ale przeszkadza w programie, w którym następują nieliniowe przełączenia wykonywania. Wiem, że w C++ są operatory “new”/“delete”, ale nie dają mi one nic więcej ponad to, co już mam w C+.
Wymieniłem powyżej dwa problemy (które teraz już zauważam) z obiektami, ale nie te problemy są najważniejsze. One tylko wskazują, iż brakuje innej koncepcji użycia (implementacji przez kompilator) obiektów w języku programowania.
Natomiast teraz zastanowię się nad sensownością użycia obiektów w dotychczasowy sposób.
Można zakładać, że obiekt umieszczony na stosie w bloku strukturalnym programu (na przykład ograniczonym klamrami “{” i “}”) zawiera wewnątrz zmienne, które i tak byłyby użyte, gdyby implementować tę funkcjonalność bez użycia klasy C++. Jednak z moich obserwacji wynika, że klasy zawierają uniwersalne funkcjonalności, które są czasem nadmiarowe względem potrzeb bieżących, więc logiczne, że zawierają wiele zmiennych, które są niepotrzebne w danym kontekście użycia, niewykorzystane. W C+ (opartym bezpośrednio na C, ale bez użycia biblioteki standardowej) rozwiązuję ten problem przez definiowanie obiektu podstawowego i obiektu rozszerzającego, który nie musi być tworzony. Na przykład mam w postaci procedur języka C — definiujących konstruktora, destruktora i metody — obiekt tablicy dynamicznej (zdaje się podobnie jak w C++ “vector”) oraz obiekt rozszerzający: ‘iterator’ elementów tablicy dynamicznej zachowujący poprawny i postępujący stan niezależnie od zmieniana zawartości tablicy. Drugi obiekt korzystając ze zmiennych pierwszego obiektu tworzy własne zmienne tylko wtedy, gdy sam zostanie utworzony, i usuwa je po jego wyrzuceniu (z pamięci), tutaj procedurą destruktora, lub zakończeniu ‘iteracji’.
Powyższy problem mógłby być rozwiązany w C++ przez przydzielanie wewnątrz klasy danych na stercie w przypadku użycia nadmiarowej funkcjonalności. Ale czy tak jest powszechnie robione? Oraz czy to nie wymaga ręcznego zarządzania tworzeniem/wyrzucaniem obiektów wewnątrz takiej klasy, ponieważ nie ma odpowiednich konstrukcji języka programowania?
Ponadto w C+ program wykonuje się w tzw. ‹zadaniach›, które są w powszechnym rozumieniu ograniczonymi wątkami (wirtualnymi lub rzeczywistymi). To powoduje konieczność definiowania zmiennych używanych przez więcej niż jedno ‹zadanie› — w przestrzeni globalnej (poza procedurami). Oczywiście pogrupowałem takie zmienne według zastosowania i umieszczam je w strukturach globalnych. W tym przypadku kompletnie nie mam koncepcji, jak w programie miało by być pomyślane użycie takich zmiennych, w jego składni.
Pytania są następujące. O czym w ogóle piszę: czy taki koncept jest już gdziekolwiek realizowany w języku programowania (w naturalny sposób) i co to za koncept? Jak w hipotetycznej składni programu umieszczać obiekty, które są współdzielone pomiędzy ‹zadaniami›? Jak realizować rozszerzalność obiektów nie w postaci rozbudowy (w C++ dziedziczenie), ale w postaci rozszerzania w przypadku użycia — a to wszystko automatyzowane w składni języka? No i oczywiście pytania z poprzednich akapitów.
⁂
Wiem mniej więcej, co miałem na myśli w tym pytaniu. Zamiast bloków strukturalnych języka programowania potrzebne mi są bloki wykonawcze, w których dane byłyby przechowywane pomiędzy procedurami, bez względu na hierarchię strukturalną programu, i usuwane na końcu. Program wykonuje się zmieniając stany obiektów. W pewnym stanie obiektów jest początek bloku wykonawczego, a w innym stanie obiektów jest koniec tego bloku wykonawczego. Ponieważ program wykonuje się centrycznie wokół wejścia danych (np. “socket”) lub wyjścia danych (np. rysowanie obiektów graficznych w oknie), a nie wokół obiektu tworzonego pobocznie, nie jest możliwe użycie bloku strukturalnego. Pewną próbą podejścia do tej sytuacji (powiązania bloku strukturalnego z wykonawczym) jest znany z języka Python blok strukturalny “with”, ale on umożliwia tylko proste użycia, które u mnie nie mają zastosowania. Globalne kolekcje obiektów, których używam (np. kolekcja otwartych plików) są tylko wąskim zastosowaniem bloku wykonawczego, tzn. tylko dla obiektu otwartego pliku. Pełne zastosowanie bloku wykonawczego wymagałoby tworzenia tymczasowych globalnych (by były dostępne pomiędzy procedurami) obiektów, w których byłyby zmienne bloku wykonawczego.