• 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

0 głosów
1,591 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 (195,220 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 (195,220 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 (158,960 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 (195,220 p.)
Tak.
1
komentarz 4 kwietnia 2017 przez mokrowski Mędrzec (158,960 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 (195,220 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ź 460 wizyt
0 głosów
1 odpowiedź 836 wizyt
pytanie zadane 25 czerwca 2018 w C i C++ przez niezalogowany
0 głosów
2 odpowiedzi 1,253 wizyt
pytanie zadane 1 maja 2016 w C i C++ przez Avernis Nałogowiec (27,400 p.)

93,733 zapytań

142,669 odpowiedzi

323,287 komentarzy

63,293 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

Twierdza Linux. Bezpieczeństwo dla dociekliwych

Aby uzyskać rabat -10%, użyjcie kodu pasja-linux, wpisując go w specjalne pole w koszyku.

...