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

Klasy czysto wirtualne i std::vector

Object Storage Arubacloud
0 głosów
723 wizyt
pytanie zadane 4 kwietnia 2017 w C i C++ przez Sidzej Użytkownik (850 p.)

Cześć, mam problem z poniższym kodem (fragmenty): 

Mam klase abstrkcyjną "Potion"

Potion.h:

#pragma once
#include <iostream>
#include <string>

class Potion
{
	std::string name;

public:
	Potion(const std::string n = "");
	Potion(const Potion & p);
	virtual ~Potion();
	void setName(std::string n) { name = n; }
	std::string getName()const { return name; }
	virtual void printPotionType()const = 0;
	bool operator==(const Potion & p)const;


	friend std::ostream & operator<<(std::ostream & os, const Potion & p);
};

i klasę pochodną "Heatlh potion"

HealthPotion.h

#pragma once
#include "Potion.h"

class HealthPotion : public Potion
{
	int power;

public:

	HealthPotion(std::string name, int p);
	~HealthPotion();
	int getPower()const { return power; }
	void printPotionType()const;
};

definicja metody printPotionType() const z pliku HealthPotion.cpp, w pliku Potion.cpp takowej nie ma.

void HealthPotion::printPotionType()const
{
	std::cout << "Health potion, power = " << power << std::endl;
}

dodatkowo jest jeszcze klasa "Player", która posiada std::vector na obiekty klasy Potion, wygląda to tak:

#pragma once
#include <iostream>
#include <vector>
#include "Item.h"
#include "Potion.h"

class Player
{
	...
	std::vector<Potion> potions;

public:
	...
	void addPotion(const Potion & p);
	void printPotions();
	void Drink(const Potion & p);
	Potion & getPotion(unsigned n);//zwraca referencje do elementu wektora potions o indeksie podanym jako argument
        ...
};

oraz definicje tych metod:

void Player::addPotion(const Potion & p)
{
	potions.push_back(p);
}

void Player::printPotions()
{
	cout << "POTIONS: " << endl;

	for(auto & p : potions)
	{
		cout << p << endl;
	}
}

void Player::Drink(const Potion & p)
{
	for (auto it = potions.begin(); it != potions.end(); it++)
	{
		if (*it == p)
		{
			cout << p.getName() << " used" << endl;
			potions.erase(it);
			break;
		}
	}

}

Potion & Player::getPotion(unsigned n)
{
	if (n <= potions.capacity())
		return potions[n];

	else
	{
		cout << "Player doesn't have so many potions" << endl;
	}
}

przy próbie kompilacji wyświetla mi się takie coś:

1>c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(737): error C2259: 'Potion': cannot instantiate abstract class
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(737): note: due to following members:
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(737): note: 'void Potion::printPotionType(void) const': is abstract
1>  c:\users\dudyc\desktop\programowanie obiektowe\c++\lista5\potion.h(15): note: see declaration of 'Potion::printPotionType'
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(857): note: see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,Potion&>(_Objty *,Potion &)' being compiled
1>          with
1>          [
1>              _Ty=Potion,
1>              _Objty=Potion
1>          ]
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(857): note: see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,Potion&>(_Objty *,Potion &)' being compiled
1>          with
1>          [
1>              _Ty=Potion,
1>              _Objty=Potion
1>          ]
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(996): note: see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,Potion&>(std::allocator<_Ty> &,_Objty *,Potion &)' being compiled
1>          with
1>          [
1>              _Alloc=std::allocator<Potion>,
1>              _Ty=Potion,
1>              _Objty=Potion
1>          ]
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0(995): note: see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,Potion&>(std::allocator<_Ty> &,_Objty *,Potion &)' being compiled
1>          with
1>          [
1>              _Alloc=std::allocator<Potion>,
1>              _Ty=Potion,
1>              _Objty=Potion
1>          ]
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\vector(1284): note: see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<_Ty,Potion&>(_Ty *,Potion &)' being compiled
1>          with
1>          [
1>              _Ty=Potion
1>          ]
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\vector(1283): note: see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<_Ty,Potion&>(_Ty *,Potion &)' being compiled
1>          with
1>          [
1>              _Ty=Potion
1>          ]
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\vector(1276): note: while compiling class template member function 'void std::vector<Potion,std::allocator<_Ty>>::push_back(const Potion &)'
1>          with
1>          [
1>              _Ty=Potion
1>          ]
1>  c:\users\dudyc\desktop\programowanie obiektowe\c++\lista5\player.cpp(30): note: see reference to function template instantiation 'void std::vector<Potion,std::allocator<_Ty>>::push_back(const Potion &)' being compiled
1>          with
1>          [
1>              _Ty=Potion
1>          ]
1>  c:\users\dudyc\desktop\programowanie obiektowe\c++\lista5\player.h(14): note: see reference to class template instantiation 'std::vector<Potion,std::allocator<_Ty>>' being compiled
1>          with
1>          [
1>              _Ty=Potion
1>          ]

Przypuszczam, że może to mieć związek z deklaracją vectora "potions" ale nie mam pojęcia z czym dokladnie.

Czy byłby ktoś w stanie wyjaśnić mi co jest w tym kodzie nie tak? Szukam już po internecie prawie dwie godziny i nie mogę znaleźć rozwiązania.

 

1 odpowiedź

0 głosów
odpowiedź 4 kwietnia 2017 przez j23 Mędrzec (194,920 p.)
edycja 4 kwietnia 2017 przez j23
 
Najlepsza
std::vector<Potion> potions;

Powinno być:

std::vector<Potion*> potions;


template <typename T> 
void Player::addPotion(const T & p)
{
    potions.push_back(new T(p));
}

lub bardziej bezpiecznie:

std::vector<unique_ptr<Potion>> potions;

template <typename T> 
void Player::addPotion(const T & p)
 { 
      potions.push_back(make_unique<T>(p)); 
}

 

 

komentarz 4 kwietnia 2017 przez j23 Mędrzec (194,920 p.)

@mokrowski, addPotion robi kopie obiektu podanego w parametrze, więc oczywistym jest, że nie mogę stworzyć instancji klasy Potion, bo ta jest klasą abstrakcyjną. Więc jak teraz da w parametrze referencję do obiektu klasy HealthPotion, to kopia tego obiektu zostanie dodana do vektora.

 

P.S. poprawiłem drobny błąd w kodzie.

komentarz 4 kwietnia 2017 przez mokrowski Mędrzec (155,460 p.)

Dobrze... mówimy o tym kodzie tak?

template<typename T>
enable_if_t<is_base_of<Potion, T>::value> Player::addPotion(const T & p)
{
      potions.push_back(make_unique<T>(p));
}

 

komentarz 4 kwietnia 2017 przez j23 Mędrzec (194,920 p.)
Tak.
1
komentarz 4 kwietnia 2017 przez mokrowski Mędrzec (155,460 p.)

No to ja to widzę tak... na marginesie myślę że fajny przypadek gdzie "wpychać smart-pointery" :-)

addPotion(...) sugeruje dodanie do ekwipunku gracza. Drugim wymaganiem jest zachowanie polimorfizmu dynamicznego na elementach kontenera. Jeśli tak, to może to mieć miejsce na następujące sposoby:

  1. Z przejęciem własności - wtedy unique_ptr jest w kontenerze ok, ale sygnatura metody jest błędna (nie może to być referencja stała). Może to być r-referencja (chyba najlepiej), referencja (już gorzej bo sugeruje zmiany w Potion), wskaźnik (jeszcze gorzej bo sugeruje że może być przypadek nullptr). W tej kolejności ważności. No .. może jeszcze w bardzo uzasadnionym przypadku unique_ptr w sygnaturze metody ale to już trzeba nieźle pomyśleć.. może ... wygoda? Z tym ostatnim nie będę się upierał. No i konsekwencją jest wkomponowanie obiektu Potion w Player.
  2. Bez przejęcia własności - wtedy w kontenerze niepotrzebny unique_ptr bo Player destrukcją się nie zajmuje. Może być w kontenerze nawet goły wskaźnik, stały goły wskaźnik lub reference_wrapper. Nie może być zwykła kopia bo jest wymaganie polimorfizmu dynamicznego.
  3. Rozwiązanie @j23 które widać wyżej. 

No to w świetle problemu przejęcia własności (bo to sugeruje unique_ptr) jaki sens ma zmuszanie kompilatora do powielania kodu dla każdej możliwej klasy wyprowadzonej z Potion? To właśnie robi szablon. Jest to i skomplikowane w kodzie i obszerne w wynikach kompilacji. Każda nowa mikstura, nowy fragment kodu Player. Tym bardziej że (o ile dobrze rozumiem domenę), Player ma agregować a nie wkomponowywać napoje/mikstury. Kopia Potion jest więc .. klonem? Hmm.. No tak.. i tą drogą ktoś zaraz pomyśli o Singletonie (!).. 

No dobrze.. ale jak się uprzeć, wiedzieć co się robi to się nie da bez szablonu użyć tego unique_ptr? Oczywiście że się da. Przecież unique_ptr ma metodę reset() zamieniającą wskaźniki. Tylko wtedy już śmiesznie nie będzie. Jak umrze Player to zniszczy mikstury a ktoś może jeszcze z nich chcieć skorzystać i będą stada UB :-/ Tu może mieć sens nawet przekazywanie shared_ptr jeśli będzie chodziło o niszczenie w trybie "ostatni gasi światło" :-)

Tak to widzę.. i przepraszam że takie długie wyszło :-/ Oczywiście wszystko jest kwestią wyborów. A dyskutować warto bo można poznać ciekawe zastosowania znanych mechanizmów.

komentarz 4 kwietnia 2017 przez j23 Mędrzec (194,920 p.)

jaki sens ma zmuszanie kompilatora do powielania kodu dla każdej możliwej klasy wyprowadzonej z Potion?

Jak już pisałem, w przypadku tworzenia kopii, innej opcji nie masz. Takie rozwiązanie dałem, bo tak zrozumiałem intencje OP - vector obiektów Potion, const referencja w argumencie. Gdyby było inaczej, dałbym inne rozwiązanie.

Jest to i skomplikowane w kodzie i obszerne w wynikach kompilacji.

Bez przesady. Wersja pierwotna, bez enable_if, nie jest jakoś przesadnie skomplikowana, a z tą ilością kodu wygenerowanego też bym nie przesadzał.

 

Podobne pytania

0 głosów
1 odpowiedź 150 wizyt
0 głosów
1 odpowiedź 584 wizyt
pytanie zadane 25 czerwca 2018 w C i C++ przez niezalogowany
0 głosów
2 odpowiedzi 410 wizyt
pytanie zadane 1 maja 2016 w C i C++ przez Avernis Nałogowiec (27,400 p.)

92,575 zapytań

141,424 odpowiedzi

319,649 komentarzy

61,960 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

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy 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!

...