Pracuję nad aplikacją średnio osadzoną w C przy użyciu technik podobnych do OO. Moje „klasy” to moduły .h / .c wykorzystujące struktury danych i struktury wskaźników funkcji do emulacji enkapsulacji, polimorfizmu i wstrzykiwania zależności.
Teraz można oczekiwać, że myModule_create(void)
funkcja będzie miała myModule_destroy(pointer)
odpowiednik. Ale ponieważ projekt jest osadzony, zasoby, które są tworzone realistycznie, nigdy nie powinny zostać uwolnione.
Mam na myśli, że jeśli mam 4 porty szeregowe UART i utworzę 4 instancje UART z wymaganymi pinami i ustawieniami, nie ma absolutnie żadnego powodu, aby kiedykolwiek chcieć zniszczyć UART # 2 w pewnym momencie podczas uruchamiania.
Czy więc zgodnie z zasadą YAGNI (nie będziesz jej potrzebować), czy powinienem pominąć destruktory? Wydaje mi się to bardzo dziwne, ale nie mogę wymyślić dla nich zastosowania; zasoby są zwalniane po wyłączeniu urządzenia.
źródło
myModule_create(void)
funkcję? Możesz po prostu zakodować określone wystąpienia, których spodziewasz się użyć w ujawnianym interfejsie.Odpowiedzi:
Glampert ma rację; tutaj nie ma potrzeby stosowania destruktorów. Tworzyliby po prostu rozdęcie kodu i pułapkę dla użytkowników (używanie obiektu po wywołaniu destruktora jest niezdefiniowanym zachowaniem).
Należy jednak upewnić się, że naprawdę nie ma potrzeby usuwania obiektów. Na przykład, czy potrzebujesz obiektu dla UART, który nie jest obecnie używany?
źródło
Najłatwiejszym sposobem na wykrycie wycieków pamięci jest czyste zamknięcie aplikacji. Wiele kompilatorów / środowisk umożliwia sprawdzanie ilości pamięci, która jest nadal przydzielana po zamknięciu aplikacji. Jeśli nie jest dostarczony, zwykle jest sposób, aby dodać kod tuż przed wyjściem, który może to rozgryźć.
Tak więc z pewnością zapewniłbym konstruktory, destruktory i logikę zamykania nawet w systemie osadzonym, który „teoretycznie” nigdy nie powinien wychodzić dla łatwego wykrywania wycieków pamięci. W rzeczywistości wykrywanie wycieków pamięci jest jeszcze ważniejsze, jeśli kod aplikacji nigdy nie powinien wyjść.
źródło
W moim rozwoju, który szeroko wykorzystuje nieprzejrzyste typy danych do promowania podejścia podobnego do OO, również zmagałem się z tym pytaniem. Początkowo byłem zdecydowanie w obozie eliminacji destruktora z perspektywy YAGNI, a także perspektywy „martwego kodu” MISRA. (Miałem dużo miejsca na zasoby, to nie było wzięcie pod uwagę.)
Jednak ... brak destruktora może utrudnić testowanie, tak jak w przypadku automatycznych testów jednostkowych / integracyjnych. Konwencjonalnie każdy test powinien obsługiwać konfigurację / porzucenie, aby obiekty mogły być tworzone, manipulowane, a następnie niszczone. Są niszczone, aby zapewnić czysty, nieskażony punkt wyjścia do następnego testu. Aby to zrobić, klasa potrzebuje destruktora.
Dlatego, z mojego doświadczenia, „aint't” w YAGNI okazuje się być „są” i ostatecznie stworzyłem niszczyciele dla każdej klasy, niezależnie od tego, czy myślałem, że będę jej potrzebować, czy nie. Nawet jeśli pominąłem testowanie, istnieje przynajmniej poprawnie zaprojektowany destruktor dla biednego sloba, który za mną podąża. (I, przy znacznie mniejszej wartości, sprawia, że kod jest bardziej użyteczny, ponieważ można go używać w środowisku, w którym zostałby zniszczony.)
Chociaż dotyczy YAGNI, nie dotyczy martwego kodu. W tym celu stwierdzam, że warunkowe makro kompilacji, takie jak #define BUILD_FOR_TESTING, pozwala wyeliminować destruktor z ostatecznej wersji produkcyjnej.
Robisz to w ten sposób: masz destruktor do testowania / przyszłego ponownego wykorzystania i spełniasz cele projektowe YAGNI i zasady „bez martwego kodu”.
źródło
Możesz mieć destruktor bez oporu, coś w rodzaju
następnie ustaw destruktor
Uart
być może za pomocą(dodaj odpowiednią obsadę, jeśli jest potrzebna)
Nie zapomnij udokumentować. Może nawet chcesz
Alternatywnie, specjalny przypadek we wspólnym kodzie wywołującym destruktor przypadek, gdy funkcja wskaźnika destruktora ma na
NULL
celu uniknięcie jego wywołania.źródło