Mam klasę CPP, której konstruktor wykonuje pewne operacje. Niektóre z tych operacji mogą się nie powieść. Wiem, że konstruktorzy nic nie zwracają.
Moje pytania są
Czy wolno wykonywać inne operacje niż inicjowanie elementów w konstruktorze?
Czy można powiedzieć funkcji wywołującej, że niektóre operacje w konstruktorze nie powiodły się?
Czy mogę
new ClassName()
ustawić wartość NULL, jeśli wystąpią jakieś błędy w konstruktorze?
object-oriented
c++
MayurK
źródło
źródło
Square
z konstruktorem, który pobiera jeden parametr, długość boku, chcesz sprawdzić, czy ta wartość jest większa niż 0.Odpowiedzi:
Tak, choć niektóre standardy kodowania mogą tego zabraniać.
Tak. Zalecanym sposobem jest zgłoszenie wyjątku. Alternatywnie możesz przechowywać informacje o błędach wewnątrz obiektu i podać metody dostępu do tych informacji.
Nie.
źródło
Można utworzyć metodę statyczną, która wykonuje obliczenia i zwraca obiekt w przypadku powodzenia lub niepowodzenia.
W zależności od tego, jak ta konstrukcja obiektu jest wykonywana, może być lepiej utworzyć inny obiekt, który pozwala na konstruowanie obiektów metodą niestatyczną.
Pośrednie wywołanie konstruktora jest często nazywane „fabryką”.
Pozwoliłoby to również zwrócić obiekt zerowy, co może być lepszym rozwiązaniem niż zwrócenie wartości zerowej.
źródło
NULL
. Na przykład, wint foo() { return NULL
rzeczywistości zwracałbyś0
(zero) obiekt jako liczbę całkowitą. Wstd::string foo() { return NULL; }
którą przypadkowo wywołaćstd::string::string((const char*)NULL)
który jest niezdefiniowane zachowanie (NULL nie wskazują na \ 0-zakończony sznurkiem).int
. Np.std::allocator<int>
Jest idealnie zdrową fabryką.@SebastianRedl podał już proste, bezpośrednie odpowiedzi, ale przydatne może być dodatkowe wyjaśnienie.
TL; DR = istnieje reguła stylu, która upraszcza konstruktorów, są tego powody, ale te powody dotyczą głównie historycznego (lub po prostu złego) stylu kodowania. Obsługa wyjątków w konstruktorach jest dobrze zdefiniowana, a destruktory będą nadal wywoływane dla w pełni skonstruowanych zmiennych lokalnych i elementów, co oznacza, że nie powinno być żadnych problemów w idiomatycznym kodzie C ++. Reguła stylu i tak się utrzymuje, ale zwykle nie stanowi to problemu - nie każda inicjalizacja musi odbywać się w konstruktorze, a szczególnie niekoniecznie w tym konstruktorze.
Jest to powszechna reguła stylu, zgodnie z którą konstruktorzy powinni robić absolutne minimum, jakie mogą, aby ustawić zdefiniowany prawidłowy stan. Jeśli Twoja inicjalizacja jest bardziej złożona, powinna być obsługiwana poza konstruktorem. Jeśli nie ma żadnej taniej do zainicjowania wartości, którą mógłby ustawić twój konstruktor, powinieneś osłabić egzekwujących prawa wymuszone przez twoją klasę, aby ją dodać. Na przykład, jeśli przydzielanie miejsca do zarządzania klasą jest zbyt drogie, dodaj stan nieprzydzielony, ale zerowy, ponieważ oczywiście występowanie stanów specjalnych, takich jak null, nigdy nie spowodowało żadnych problemów. Ahem.
Chociaż powszechne, z pewnością w tej ekstremalnej formie jest bardzo dalekie od absolutnego. W szczególności, jak wskazuje mój sarkazm, jestem w obozie, który mówi, że osłabienie niezmienników jest prawie zawsze zbyt wysoką ceną. Istnieją jednak powody stojące za regułą stylu i istnieją sposoby na uzyskanie zarówno minimalnych konstruktorów, jak i silnych niezmienników.
Przyczyny dotyczą automatycznego czyszczenia destruktora, szczególnie w obliczu wyjątków. Zasadniczo musi istnieć dobrze określony punkt, w którym kompilator staje się odpowiedzialny za wywoływanie destruktorów. Podczas gdy nadal jesteś w wywołaniu konstruktora, obiekt niekoniecznie jest w pełni skonstruowany, więc wywołanie destruktora dla tego obiektu jest nieprawidłowe. Dlatego odpowiedzialność za zniszczenie obiektu przenosi się na kompilator dopiero po pomyślnym zakończeniu konstruktora. Jest to znane jako RAII (Resource Allocation Is Initialization), co nie jest tak naprawdę najlepszą nazwą.
Jeśli wyjątek występuje w konstruktorze, wszystko, co jest częściowo zbudowane, musi zostać jawnie wyczyszczone, zazwyczaj w pliku
try .. catch
.Jednak komponenty obiektu, które zostały już pomyślnie zbudowane, są już odpowiedzialnością kompilatorów. Oznacza to, że w praktyce nie jest to nic wielkiego. na przykład
Ciało tego konstruktora jest puste. Tak długo, jak konstruktorzy dla
base1
,member2
imember3
są bezpieczne wyjątek, nie ma nic się martwić. Na przykład, jeśli konstruktormember2
rzuca, ten konstruktor jest odpowiedzialny za samo oczyszczenie. Bazabase1
została już całkowicie zbudowana, więc jej destruktor zostanie automatycznie wywołany.member3
nigdy nie był nawet częściowo zbudowany, więc nie wymaga czyszczenia.Nawet gdy istnieje ciało, zmienne lokalne, które zostały w pełni skonstruowane przed zgłoszeniem wyjątku, zostaną automatycznie zniszczone, tak jak każda inna funkcja. Konstruktory, które żonglują surowymi wskaźnikami lub „posiadają” jakiś stan niejawny (przechowywany gdzie indziej) - zwykle oznaczający, że wywołanie funkcji rozpoczęcia / nabycia musi być dopasowane do wywołania zakończenia / zwolnienia - może powodować wyjątkowe problemy z bezpieczeństwem, ale prawdziwy problem nie zarządza zasobem poprawnie za pośrednictwem klasy. Na przykład, jeśli zastąpisz surowe wskaźniki
unique_ptr
konstruktorem, destruktor dlaunique_ptr
będzie wywoływany automatycznie w razie potrzeby.Są jeszcze inne powody, dla których ludzie preferują konstruktorów wykonujących minimalne czynności. Po pierwsze dlatego, że istnieje reguła stylu, wiele osób uważa, że wywołania konstruktora są tanie. Jednym ze sposobów na uzyskanie tego, ale wciąż silnych niezmienników, jest oddzielna klasa fabryki / konstruktora, która ma osłabione niezmienniki i która ustawia potrzebną wartość początkową przy użyciu (potencjalnie wielu) normalnych wywołań funkcji członka. Po uzyskaniu potrzebnego stanu początkowego przekaż ten obiekt jako argument konstruktorowi klasy z silnymi niezmiennikami. To może „ukraść wnętrzności” obiektu o słabych niezmiennikach - przenieść semantykę - co jest tanią (i zwykle
noexcept
) operacją.Oczywiście możesz zawinąć to w
make_whatever ()
funkcję, więc osoby wywołujące tę funkcję nigdy nie muszą widzieć instancji klasy osłabionej niezmienniki.źródło
main
funkcji lub zmiennych statycznych / globalnych. Obiekt przeznaczono użyciunew
, nie jest własnością do momentu przypisania tej odpowiedzialności, ale inteligentne wskaźniki właścicielem obiektów sterty przydzielone one odniesienia, a pojemniki są właścicielami swoich struktur danych. Właściciele mogą usunąć wcześniej, właściciel destructor jest ostatecznie odpowiedzialny.