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

question-closed Kolekcja klasy abstrakcyjnej

VPS Starter Arubacloud
0 głosów
421 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 Mędrzec (194,920 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 Mędrzec (155,460 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,590 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,590 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,590 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 Mędrzec (155,460 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 (106,230 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 Mędrzec (155,460 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,590 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 (106,230 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
1 odpowiedź 644 wizyt
0 głosów
0 odpowiedzi 574 wizyt

92,453 zapytań

141,262 odpowiedzi

319,088 komentarzy

61,854 pasjonatów

Motyw:

Akcja Pajacyk

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

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...