• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

question-closed Kolekcja klasy abstrakcyjnej

0 głosów
133 wizyt
pytanie zadane 5 kwietnia 2017 w C i C++ przez andrzejd1905 Nowicjusz (190 p.)
zamknięte 5 kwietnia 2017 przez andrzejd1905

Witam,

napotkałem na problem przy tworzeniu kolekcji klasy abstrakcyjnej (tj. klasa zarządzająca kolekcją), chciałem aby klasa ShapeContainer zażądała wskaźnikami typu Shape, ale przy tworzeniu z surowymi wskaźnikami ta klas nie zarządza wskaźnikami (co też powoduje, że np. w pewnym momencie już nie będą "żyły" obiekty, na które wskazuje kolekcja), a przy unique_ptr mam błąd, że nie można utworzyć instancji klasy. Wrzucam kod z gita: kod :D Osobiście nic nie przychodzi mi do głowy, jakby ktoś coś miał pomysł (tj. czy da się zarządzać wskaźnikami z poziomu klasy?) tudzież jakaś podpowiedź, było by spoko wink

3majcie się laugh

 

//edit

problem rozwiązany

dla potomnych zostawiam gita z poprawnym rozwiązaniem: shapecontainer

komentarz zamknięcia: problem rozwiązany

2 odpowiedzi

+1 głos
odpowiedź 5 kwietnia 2017 przez j23 VIP (100,440 p.)
wybrane 5 kwietnia 2017 przez andrzejd1905
 
Najlepsza

Temat podobny do ---> https://forum.pasja-informatyki.pl/241477/klasy-czysto-wirtualne-i-std-vector

 

void ShapeContainer::addShape(Shape& sh) {
	shapes.push_back(std::make_unique<Shape>(sh));
}

Tak to nie zadziała. Jeśli już to:

shapes.push_back(std::unique_ptr<Shape>(&sh));

Ale ta konstrukcja nie jest zbyt bezpieczna, ponieważ w argumencie mogą być referencje do obiektów stworzonych na stosie, i wtedy dupa.

void ShapeContainer::addShape(std::unique_ptr<Shape> sh) 
{ 
        shapes.push_back(move(sh)); 
}

Ta opcja wymusi użycie unique_ptr przy wywołaniu addShape, co będzie jasnym sygnałem, że jest przejęcie własności.

 

 

 

komentarz 5 kwietnia 2017 przez mokrowski VIP (102,940 p.)

Dla kompletu jeszcze:

void ShapeContainer::addShape(Shape&& sh) {
        shapes.push_back(std::unique_ptr<Shape>(&sh));
}

I przesłać trzeba przez std::move lub jawną r-ref. a potencjalne konsekwencje jak w przypadku 2 @j23. Prowokuje do przesłania czegoś ze stosu.

komentarz 5 kwietnia 2017 przez andrzejd1905 Nowicjusz (190 p.)

danke człowieku laugh myślałem że to chyba już próbowałem, ale ze zmęczenia mi się tylko wydawało xd

zadziałała metoda

void ShapeContainer::addShape(std::unique_ptr<Shape> sh) 
{ 
        shapes.push_back(move(sh)); 
}

 

+1 głos
odpowiedź 5 kwietnia 2017 przez Criss Mędrzec (169,500 p.)

Masz troche szczęścia, że niedawno miałem problem dzięki któremu dowiedziałem się jednej rzeczy, bo pewnie siedziałbyś kilka dni jak ja :D

Żeby w ogóle móc utworzyć obiekt, wystarczy, że zamiast..
 

ShapeContainer sc = ShapeContainer();

..napiszesz po prostu..

ShapeContainer sc;

Teraz próbujesz uruchomić konstruktor przenoszący. Tak się składa, że konstruktor przenoszący i przenoszący operator przypisania nie są tworzone, gdy klasa zawiera "user-declared destructor" (więcej na ten temat niżej). U ciebie tak jest. No ale wtedy powinien się uruchomić konstruktor kopiujący, prawda? Gdyby taki był :D Konstruktor kopiujący (i kopiujący operator przypisania) również zostały usunięte z powodu membera std::vector<std::unique_ptr<Shape>> który jest niekopiowalny z powodu przetrzymywanego typu danych (std::unique_ptr<T> jak sama nazwa wskazuje, nie ma konstruktora kopiującego). Także po prostu nie miał się jaki konstruktor uruchomić. Inna sprawa, że było to w ogóle niepotrzebne, bo chcesz zainicjalizować obiekt obiektem utworzonym za pomocą domyślnego konstruktora.

Także najlepiej nie definiuj niepotrzebnie destruktora, a jak już koniecznie chcesz zaznaczyć jego obecność, to poprzez =default. A jeśli z jakiegoś powodu jesteś zmuszony zdefiniować destruktor i zależy ci na przenoszeniu (tutaj zdecydowanie ci zależy, bo nie masz możliwości kopiowania), to defaultuj (=default) konstruktor przenoszący i przenoszący operator przypisania (chociaż rzadko, się zdarza, żeby była konieczność definiowania destruktora i brak konieczności definiowania wspomnianego konstruktora i operatora).

Odnośnie tego deletowanego move ctora: tutaj mój temat na SO. W odpowiedzi Igora jest zacytowany standard.

komentarz 5 kwietnia 2017 przez andrzejd1905 Nowicjusz (190 p.)
dobra, to mówisz żeby zostawiać w ShapeContainer defaultowy konstruktor, a czy w Shape i pochodnych też tak robić?
komentarz 5 kwietnia 2017 przez Criss Mędrzec (169,500 p.)

Nic nie mówiłem o zostawianiu ani nie-zostawianiu defaultowego konstruktora. Jeśli pytasz, czy nie robić tak: ShapeContainer sc = ShapeContainer(); - to tak, nie robić tak. Bo po co?

komentarz 5 kwietnia 2017 przez andrzejd1905 Nowicjusz (190 p.)

to dalej jest ten sam błąd

Severity	Code	Description	Project	File	Line	Suppression State
Error	C2259	'Shape': cannot instantiate abstract class	ShapeContainer	c:\program files (x86)\microsoft visual studio 14.0\vc\include\memory	1630	

 

komentarz 5 kwietnia 2017 przez Criss Mędrzec (169,500 p.)
edycja 5 kwietnia 2017 przez Criss

Czyli błąd dotyczy Shape a nie ShapeContainer. Przyczyna jest w metodzie ShapeContainer::addShape. Próbujesz utworzyć obiekt klasy abstrakcyjnej (klasa Shape ma metody czysto wirtualne).

Na przyszłość: dokładnie sprecyzuj błąd (najlepiej wklej) i podaj w której dokładnie linii kodu występuje.

PS: to co napisałem wyżej nadal obowiązuje i dobrze jakbyś to poprawił :) Tutaj kompilator jakoś był w stanie to ominąć dzięki optymalizacjom, ale taki kod:

ShapeContainer s;
ShapeContainer sc = s;

ani taki:

ShapeContainer s;
ShapeContainer sc = std::move(s);

już sie nie skompilują

komentarz 5 kwietnia 2017 przez mokrowski VIP (102,940 p.)

Tu jest błąd koncepcyjny. Jeśli ShapeContainer ma przejąć własność obiektów Shape, to unique_ptr jest ok. Jeśli nie, to są inne metody zachowania polimorfizmu i budowania kontenera. Podstawowe pytanie czy to ma być Agregacja czy Kompozycja.

Wskaźnik unique_ptr służy do przejęcia własności a nie jako "tani sprzątacz".

komentarz 5 kwietnia 2017 przez andrzejd1905 Nowicjusz (190 p.)

thx panowie, mimo, że wasz propozycje w tym problemie nie zadziałały, to i tak z waszych odp trochę się dowiedziałem, rzeczy które może w przyszłości mi się przydadzą smiley

3majcie się

komentarz 19 kwietnia 2017 przez draghan VIP (102,850 p.)

Sorry za (nieduży) odkop, ale coś tu chyba trzeba naprostować. :)

Żeby w ogóle móc utworzyć obiekt, wystarczy, że zamiast..

ShapeContainer sc = ShapeContainer();

..napiszesz po prostu..

ShapeContainer sc;

Teraz próbujesz uruchomić konstruktor przenoszący.

Jeśli mówisz tutaj o jakimś specyficznym kontekście, którego nie złapałem, to może i owszem. Ale proszę mnie poprawić, jeśli nie jest tak, że oba zapisy wywołają... konstruktor domyślny? smiley

#include <iostream>

struct Foo
{
    Foo()
    {
        std::cout<<"ctor\n";
    }
    Foo(const Foo &f)
    {
        std::cout<<"cp ctor\n";
    }
    Foo(const Foo &&f)
    {
        std::cout<<"mv ctor\n";
    }
    Foo& operator=(const Foo &f)
    {
        std::cout<<"=\n";
        return *this;
    }
    ~Foo()
    {
        std::cout<<"dtor\n";
    }
};

int main()
{
    {
        Foo f = Foo();
    }
    {
        std::cout<<"*\n";
        Foo f;
    }
}
ctor
dtor
*
ctor
dtor

 

2
komentarz 19 kwietnia 2017 przez mokrowski VIP (102,940 p.)

No.. i to jest właśnie "magic". Patrząc na:

Foo f = Foo();

.. widzisz (przy odrobinie złej woli i patrząc z punktu widzenia IQ marchewki) wywołanie konstruowania Foo() po prawej stronie i przypisanie operatorem= do nazwy f. Jak trochę IQ wzrośnie, widzisz że to "tworzenie obiektu po prawej" i .. przeniesienie do lewej czyli konieczność uruchamiania konstruktora przenoszącego.. tymczasem... 

Teraz popraw fragment kodu na taki:

    {                                                                              
        std::cout<<"cp ctor\n";                                                    
    }                                                                              
    Foo(const Foo &&) = delete;                                                    
    /*                                                                             
    {                                                                              
        std::cout<<"mv ctor\n";                                                    
    }                                                                              
    */                                                                             
    Foo& operator=(const Foo &)                                                    
    {                                                                              
        std::cout<<"=\n";                                                          
        return *this;                                                              
    }

.. I oto się nie kompiluje z informacją że przenoszący konstruktor jest wyrzucony :-) A z konstruktorem uruchamia domyślny :-)

Ano jest tak dlatego że komisja od C++11 wpisała że kompilator może tu robić optymalizację uruchamiania domyślnego konstruktora i kluczowym jest słowo "może". Gcc, Clang, kompilator IBM'a, Oracle tak robił ale ... MS VS w trybie debug.. nie :-) Dopiero od C++17 wpisano że kompilator musi robić tę optymalizację. Czyli w zasadzie (prawie) wszyscy robią tu optymalizację ale.. wymagają składni jakby jej nie było .. :-/ 

Jednym słowem czy Ci się podoba czy nie... tak jest :-)

1
komentarz 19 kwietnia 2017 przez Criss Mędrzec (169,500 p.)

@draghan - u ciebie uruchamia się destruktor domyślny, ale tylko dzięki optymalizacjom kompilatora. Gdybyś jawnie (=delete) zabronił tworzenia copy ctora, to by się nie skompilowało (brak zarówno copy jak i move ctora). W każdym razie nie przed c++17, bo wydaje mi się, że guaranteed copy ellision załatwiłoby tu sprawe i skompilowałby to.

Live przykład

Btw:

Foo(const Foo &&f)

const rvalue referencja nie bardzo ma sens. Oczywiście tutaj bez znaczenia, ale tak ogólnie.

Jeśli mówisz tutaj o jakimś specyficznym kontekście, którego nie złapałem, to może i owszem.

W zasadzie.. dość specyficznym, bo copy ctor został usunięty z oczywistych powodów, a move z powodu "user-declared destructor". Także nie ma ani tego ani tego, czego twój kod nie uwzględnia :) 

komentarz 19 kwietnia 2017 przez draghan VIP (102,850 p.)

Ano jest tak dlatego że komisja od C++11 wpisała że kompilator może tu robić optymalizację uruchamiania domyślnego konstruktora i kluczowym jest słowo "może". Gcc, Clang, kompilator IBM'a, Oracle tak robił ale ... MS VS w trybie debug.. nie :-) Dopiero od C++17 wpisano że kompilator musi robić tę optymalizację. Czyli w zasadzie (prawie) wszyscy robią tu optymalizację ale.. wymagają składni jakby jej nie było .. :-/ 

Czyli odkop się opłacił. Dzięki. yes

const rvalue referencja nie bardzo ma sens. Oczywiście tutaj bez znaczenia, ale tak ogólnie

No jasne, to z rozpędu. ;)

Podobne pytania

0 głosów
0 odpowiedzi 116 wizyt
0 głosów
1 odpowiedź 125 wizyt
Porady nie od parady
Nie wiesz jak poprawnie zredagować pytanie lub pragniesz poznać którąś z funkcji forum? Odwiedź podstronę Pomoc (FAQ) dostępną w menu pod ikoną apteczki.FAQ

63,199 zapytań

109,435 odpowiedzi

228,618 komentarzy

42,944 pasjonatów

Przeglądających: 76
Pasjonatów: 8 Gości: 68

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...