C++ - 3/5/0

0

Po burzliwej dyskusji... chcę wrócić do zasady 3/5/0

znalazłem ten wątek
i chciałbym zapytać:

  1. Jak można uniknąć pisania konstruktora ? Nie chodzi mi o uniknięcie samo w sobie (bo mogę go po prostu nie tworzyć) ale o to jak stworzę obiekt bez konstruktora ? Przecież i tak stworzy się obiekt z domyślnym konstruktorem, a jeżeli zdefiniuje swój konstruktor, to będę miał obiekt startowy z parametrami które chcę aby były - więc jaki jest sens tej zasady ?
  2. Co do konstruktora kopiującego - są przypadki, że domyślny konstruktor kopiujący nie wystarczy np w przypadku tworzenia macierzy, to dlaczego też jest zalecane unikanie pisania konstruktora kopiującego ?
  3. destruktor tak samo...

jeżeli chodzi o unique_ptr to już w ogóle nie wiem jak przenieść zasoby do tego wskaźnika... i po co ?

1

"bo ktoś niepoatrznie użyje konstruktora kopiującego mimo, że go nie zdefiniowałem"

Nie zdefiniowałeś, to go nie mógł użyć.
Użył kopiowania obiektu (defaultowego), którego nie zablokowałeś.

Zrozum chłopie, bardzo Ciężko ci będzie o postęp, jeśli widzisz doskonałość w sobie i wrogie knowania u reszty świata

1
zkubinski napisał(a):
  1. Jak można uniknąć pisania konstruktora ? Nie chodzi mi o uniknięcie samo w sobie (bo mogę go po prostu nie tworzyć) ale o to jak stworzę obiekt bez konstruktora ? Przecież i tak stworzy się obiekt z domyślnym konstruktorem, a jeżeli zdefiniuje swój konstruktor, to będę miał obiekt startowy z parametrami które chcę aby były - więc jaki jest sens tej zasady

Zasada 3/5/0 w ogóle nie mówi o „zwykłych” konstruktorach, tylko o konstruktorze kopiującym i konstruktorze przesuwającym.

Zasada mówi, że w klasie

  • albo definiujemy destruktor, konstruktor kopiujący i operator przypisania, albo
  • to co powyżej plus konstruktor przesuwający i odpowiadający mu operator, albo
  • nie definiujemy żadnej z tych rzeczy.

Jeśli klasa nie pasuje do żadnej z tych trzech kategorii, prawdopodobnie coś jest źle.

?

W języku polskim nie stawiamy spacji przed znakiem zapytania.

1

jeżeli chodzi o unique_ptr to już w ogóle nie wiem jak przenieść zasoby do tego wskaźnika... i po co ?

smart pointery ogólnie mają za zadanie zarządzać czasem życia zasobów, żebyś Ty nie musiał koniecznie o tym pamiętać (właśnie dlatego kiedy mądrze ich używasz nie musisz np. samemu implementować destruktorów) Taki unique pointer dodatkowo gwarantuje Ci, że tylko on ma dostęp w danym momencie do danego zasobu. Zachowanie to bywa często bardzo pożądane (np. w aplikacjach wielowątkowych) i ogólnie jeśli nie ma ku temu powodów lepiej używać unique pointerów niż shared pointerów. Co do tego jak używać to polecam: https://en.cppreference.com/w/cpp/memory/unique_ptr/operator%3D

0

dobra, a więc tak, po przeczytaniu linka - The rule of 3/5/0
rozumiem następująco

  1. Zasada 3
    Jeżeli klasa wymaga destruktora, konstruktora kopiującego lub operatora przypisania, to najprawdopodobniej potrzebuje ich wszystkich trzech - czyli już na dzień dobry mam sam określić czy w mojej klasie będą wymagane te składowe, jeżeli nie jestem pewny czy ich potrzebuję, to dla bezpieczeństwa lepiej je zdefiniować.

  2. Zasada 5
    Jeżeli mam zdefiniowany destruktor, konstruktor kopiujący i operator przypisania to ta zasada implikuje zasadę 3 i trzeba dołożyć jeszcze dwie składowe konstruktor przenoszący i przenoszący operator przypisania

  3. Zasada 0
    Implikuje zasadę 5 - czyli jeżeli w klasie zdefiniowany jest destruktor, konstruktor kopiujący, operator przypisania, konstruktor przenoszący i przenoszący operator przypisania to te składowe jak i klasa powinna zajmować się własnymi składowymi klasy. Czyli w klasie nie powinno być definicji i deklaracji żadnych obcych obiektów. - Ale jak to rozumieć że ma się zajmować tylko "sobą" ? Chodzi o SRP ?
    Klasy które nie spełniają zasady 5 - mogą uniknąć pisania destruktora, konstruktora kopiującego, operatora przypisania, konstruktora przenoszącego, przenoszącego operatora przypisania o ile użyjemy wbudowanych typów, które obsługują odpowiednią semantykę kopiowania i przenoszenia (np. jak użyjemy jakiegoś pojemnika np std::vector)

Tylko nie wiem co z klasami polimorficznymi.

Czy dobrze rozumiem ?

2

Czytałeś jakąś książkę na temat C++? To są dość elementarne rzeczy, całkiem proste w zrozumieniu jeżeli się już coś pisało.

zkubinski napisał(a):

dobra, a więc tak, po przeczytaniu linka - The rule of 3/5/0
rozumiem następująco

  1. Zasada 3
    Jeżeli klasa wymaga destruktora, konstruktora kopiującego lub operatora przypisania, to najprawdopodobniej potrzebuje ich wszystkich trzech - czyli już na dzień dobry mam sam określić czy w mojej klasie będą wymagane te składowe, jeżeli nie jestem pewny czy ich potrzebuję, to dla bezpieczeństwa lepiej je zdefiniować.

Nie, nie masz o tym myśleć na dzień dobry. Jeżeli w trakcie pisania okaże się, że potrzebujesz jednego z tych trzech, wtedy należy przemyśleć, czy nie potrzeba też dwóch pozostałych. Na przykład, jeżeli masz

class Foo
{
    int* array;
    Foo(int size) : array{new size} 
    {}
}

E: oczywiście nie żadne new size tylko new int[size]

Łatwo zauważyć, że potrzebny jest destruktor do zwolnienia pamięci zablokowanej przez new. Teraz myślisz, skoro destruktor, to pewnie i kopiujący konstruktor i kopiujący operator przypisania? Tak, przecież kopiowanie składowa po składowej nie wystarczy, gdy mamy dynamicznie alokowaną pamięć.

  1. Zasada 5
    Jeżeli mam zdefiniowany destruktor, konstruktor kopiujący i operator przypisania to ta zasada implikuje zasadę 3 i trzeba dołożyć jeszcze dwie składowe konstruktor przenoszący i przenoszący operator przypisania

Zasada 5 powstała przez to, że od C++11 mamy move semantics, czyli to po prostu to co było kiedyś + dodatkowy konstruktor przenoszący i przenoszący operator przypisania. Więc generalnie masz rację.

  1. Zasada 0
    Implikuje zasadę 5 - czyli jeżeli w klasie zdefiniowany jest destruktor, konstruktor kopiujący, operator przypisania, konstruktor przenoszący i przenoszący operator przypisania to te składowe jak i klasa powinna zajmować się własnymi składowymi klasy. Czyli w klasie nie powinno być definicji i deklaracji żadnych obcych obiektów. - Ale jak to rozumieć że ma się zajmować tylko "sobą" ? Chodzi o SRP ?

Chodzi o to, że nie musisz żadnych zasobów zwalniać[bo na przykład jako składowe masz same zmienne typu prostego, jak int, czy zmienne typu, który sam dba o zwalnianie swoich zasobów, jak std::vector], więc nie ma potrzeby pisania innych niż domyślne operacje.

Klasy które nie spełniają zasady 5 - mogą uniknąć pisania destruktora, konstruktora kopiującego, operatora przypisania, konstruktora przenoszącego, przenoszącego operatora przypisania o ile użyjemy wbudowanych typów, które obsługują odpowiednią semantykę kopiowania i przenoszenia.

Tylko nie wiem co z klasami polimorficznymi.

Ale co ma być z klasami polimorficznymi, jaki to ma związek?

1

Tutaj jest to fajnie opisane. Zwróć uwagę na zależności między tym co definiujesz a jak reaguje na to kompilator(tabelka)

Poza tym algorytmy np. mogą optymalizować działanie na podstawie tego co klasa definiuje, np.std::is_trivially_copyable

0
Tenonymous napisał(a):

Ale co ma być z klasami polimorficznymi, jaki to ma związek?

czy w klasach polimorficznych trzeba stosować te zasady ? No chyba, że w tym przypadku klasy polimorficzne rządzą się innymi prawami ?

1

Nie trzeba. Nie trzeba też ich stosować w zwykłych klasach - to nie jest żaden wymóg.

Natomiast na pewno powinno się, i ciężko mówić o dobrym programiście C++, który się do tego nie stosuje. Czy to w klasach nie będących częścią hierarchii dziedziczenia, czy to "polimorficznych".

1

W klasach polimorficznych normą jest blokowanie wszelkiego rodzaju kopiowania/przenoszenia aby uniknąć slicingu. Natomiast używanie wewnątrz nich, oraz do trzymania uchwytów do nich typów zgodnych z w/w. zasadami to również dobra praktyka.

1 użytkowników online, w tym zalogowanych: 0, gości: 1