• 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
139 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 (105,420 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 (108,160 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 (172,320 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 (172,320 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 (172,320 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 (108,160 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,870 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 (108,160 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 (172,320 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,870 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 122 wizyt
0 głosów
1 odpowiedź 133 wizyt
Porady nie od parady
Wynikowy wygląd pytania, odpowiedzi czy komentarza, różni się od tego zaprezentowanego w edytorze postów. Stosuj więc funkcję Podgląd posta znajdującą się pod edytorem, aby upewnić się, czy na pewno ostateczny rezultat ci odpowiada.Podgląd posta

64,981 zapytań

111,458 odpowiedzi

234,638 komentarzy

46,802 pasjonatów

Przeglądających: 208
Pasjonatów: 6 Gości: 202

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.

...