C++ Unklarheiten zu virtuellen Funktionen

CA3D0

Lt. Commander
Registriert
Apr. 2011
Beiträge
1.141
Hi,

mir ist noch nicht ganz klar wie virtuelle Funktionen in C++ funktionieren, mich interessiert solcher code:
Code:
#include <iostream>
class A{
public:
	virtual void f(){
		std::cout << "A::f()" << std::endl;
	}
};
class B: public A{
public:
	void f(){
		std::cout << "B::f()" << std::endl;
	}
};
int main(void){
	B b;
	A a = b;
	a.f();
}

Hier wird die Funktion A::f() aufgerufen, ich würde jedoch gerne B::f() aufrufen, ist dies ohne Laufzeit-typinformationen möglich?
Oder ist das nur über Funktionspointer lösbar?
 
Zuletzt bearbeitet:
Das ist gerade andersrum als Du's denkst:
Code:
class A {
public:
   virtual void f() {  std::cout << "A::f()" << std::endl;  }
   void showme_from_a() { f(); }
};
class B : public A {
public:
   void f() {  std::cout << "B::f()" << std::endl; }
};
int main(void) {
   B b;
   b.showme_from_a();
}
 
Das entscheidende ist, dass ich irgendwo am liebsten ein A a = b; hätte und dann irgendwie das f() von B aufrufen könnte
Oder ist das nur mit einem von den möglich?
Code:
A& a = b;
A* a = &b;
damit wäre es nicht ohne dynamische Allocation möglich zu schreiben:
Code:
class A{
public:
	virtual void f();
};
class B: public A{
public:
	void f(){
		std::cout << "B::f()" << std::endl;
	}
};
class C: public A{
public:
	void f(){
		std::cout << "C::f()" << std::endl;
	}
}
int main(void){
	int x;
	A a = x ? B() : C();
	a.f();
}
 
Zuletzt bearbeitet:
Ich glaube besser als so wie hier kann mans kaum sagen:
Why do we need Virtual Functions in C++?

Warum jetzt in deiner main a.f(); aber nicht B::f() aufruft wundert mich auch gerade - das ist ja der Sinn von virtual.

Das hier funktioniert aber, oder?
Code:
A* a = &b;
a->f();
 
Zuletzt bearbeitet:
Bei dem Aufruf von
Code:
A a = b;
wird sog. "Slicing" betrieben.
In die Variable a vom Typ A passt nur ein A (größenmäßig), somit wird ein Teil (Slice) von B als A kopiert. Somit steht fest, dass a != b ist.
Hab ich auch nicht gewusst und musste auch erstmal suchen. C++ ist eine Weile her.

Quelle: http://stackoverflow.com/a/11671092
 
CA3D0 schrieb:
Das entscheidende ist, dass ich irgendwo am liebsten ein A a = b; hätte und dann irgendwie das f() von B aufrufen könnte

Wozu soll das gut sein? Warum nicht B b; und b.f()?
 
Egal mit was du
Code:
A a
initialisierst, es wir immer ein objekt vom typ A sein und somit wird
Code:
a.f()
immer
Code:
A::f
aufrufen.
Laufzeit Polymorphismus funktioniert nur bei Referenzen/Zeigern:
Code:
B b;
C c;
bool x = true;
A* a = x ? (A*)&b : (A*)&c;
a->f();
Hier erzeugst du kein neues Objekt, sondern einen Zeiger.
 
Zuletzt bearbeitet:
Wenn ich also eine Struct habe mit einem Member von Typ A kann ich nicht einfach den Typ B rein schreiben wegen slicing, sondern muss die Instanz von B irgendwo auf dem Stack per Referenz oder Heap per Pointer haben?!

Dann löse ich mein konkretes Problem wohl mit Funktionspointern
 
Vermutlich. Aus reiner Neugierde: Was ist denn dein konkretes Problem?
Was übrigens geht (falls du was komplexeres als ne einfache Funktion brauchst):

Code:
#include <functional>
#include <iostream>

class B {
public:
	void operator()() {
		std::cout << "B::f()" << std::endl;
	}
};
class C {
	int _p;
public:
	C(int param) : _p(param) {};
	void operator()() {
		std::cout << "C::f(). Param:" << _p << std::endl;
	}
};

int main() {
	bool x = false;
	std::function<void(void)> f;
	if (x) {
		f = B();
	} else {
		f = C(3);
	}

	f();
}
 
Zuletzt bearbeitet:
sondern muss die Instanz von B irgendwo auf dem Stack per Referenz oder Heap per Pointer haben?!
Wieso Stack und Ref und Heap Pointer? Es geht einfach darum, dass A wirklich immer Typ A ist und daher deine Zuweisung A a = b dann an a kein B::f durchführt. Aber es ginge sicherlich A* pa = pb und pa->f().
virtual sorgt dafür, dass automatisch die speziellere Funktion aufgerufen wird wie du willst nämlich B::fp
 
Zuletzt bearbeitet:
CA3D0 schrieb:
Wenn ich also eine Struct habe mit einem Member von Typ A kann ich nicht einfach den Typ B rein schreiben wegen slicing ...

Das hat nicht primär was mit slicing zu tun, sondern liegt einfach darin begründet, dass dein Objekt vom Typ A ist. Du kannst machen was du willst; legst du mal ein Objekt vom Typ A an, bleibt es auch vom Typ A, egal was für Zuweisungen du an ihm vornimmst.

Das snippet:

Code:
B b;
A a = b;

macht nicht aus dem Objekt a (welches ja den Typ A hat) ein Objekt vom Typ B.
 
Okay, dann ist das ganze klar. Ich werde das ganze wohl auf eine relativ funktionale Art lösen.

Das konkrete Problem ist, dass ich eine Simulationsroutine schreibe in welcher zur Laufzeit verschiedene Algorithmen gewählt werden können, ich wollte diese in einer Klasse kapseln welche daraufhin in einer Struct gespeichert wird. Aufgrund der Struktur des Programms kann ich nicht einfach die Referenz auf eine Instanz auf dem Stack nehmen und das ganze dynamisch zu allokieren ist unpraktisch für caches, somit werde ich wohl ein paar Funktionspointer rumreichen
 
Aufgrund der Struktur des Programms kann ich nicht einfach die Referenz auf eine Instanz auf dem Stack nehmen und das ganze dynamisch zu allokieren ist unpraktisch für caches, somit werde ich wohl ein paar Funktionspointer rumreichen
Das hört sich viel zu kompliziert an!
Du solltest Objekte natürlich niemals bewegen oder kopieren oder unnötig allokieren aber du kannst doch lokal einen Pointer (Typ Oberklasse) benutzen und darüber die Funktionen aufrufen und erreichst dadurch immer den speziellen Aufruf wie B::f.
 
Mir ist nicht ganz klar wie du das meinst
Code:
A f(x){
	return B()
}
Macht slicing

Code:
A* f(x){
	return &B()
}
Ist undefined behavior

Bei allen varianten mit globalen Variablen/Static kann ich nur eine Instanz der Klasse haben. Deshalb nutze ich einfach einen Funktionspointer
Code:
class A{
	void (*f) (X);
}
jede abgeleitete Klasse muss darf dann nur keine eigenen Member haben und muss den Funktionspointer richtig initialisieren
 
Evtl ist dein Anwendungsfall kompliziert aber ich verstehe immernoch nicht das Problem. Deine Ursprungsfrage war doch, wie du die spezialisierte Funktion aufrufst und das erreicht man mit virtual.
Ich finde auch all deine 3 Quellcode-Beispiele komisch.
Die Funktion returned ein komplettes Objekt? Warum? Ist es eine Factory? Singleton?
Ich würde zB auch nicht erwarten, dass meine Klasse Student über eine public member Funktion in der Lage ist neue Studenten zu erzeugen außer eben für Fälle wie .clone()
f ist eine Member-Funktion und soll eine Instanz auf die Klasse selbst zurückgeben? Sowas wie .clone() oder .deepcopy()?

Wieso gibst du nicht ne Referenz oder nen Pointer auf ein bereits existierendes Objekt als Argument in die Funktion rein, wie in dem stackoverflow Link den ich ganz oben gepostet hatte mit dem "void func(Animal *xyz) { xyz->eat(); }".

Evtl versteh ich dich nur nich... poste am besten mal etwas mehr Code der das Problem zeigt oder mach ein realistischeres Beispiel weil deinen einfachen A, B Fall kann man ja problemlos mit einem A* p = &b; p->f(); lösen um B::f aufzurufen.
 
Falls du ein Interface A hast, und zur Laufzeit dynamisch Implementierungen B von A aus einer Factory returnen willst,
die dann benutzt werden sollen, würde ich in der Factory std::unique_ptr<A> returnen, die dann via std::make_unique für B gebaut werden (an virtuelle Destruktoren denken!). Dann hat man zwar eine dynamische Allokation, die im Allgemeinen aber nur schwer vermeidbar ist, da Implementierungen B prinzipiell beliebig gross sein koennen. (Wenn du es unbedingt vermeiden willst und das Teil auf dem Stack leben kann, kannst du einen hinreichend grossen Buffer auf dem Stack haben, in den du ein korrekt aligntes B mit placement new baust :) ).

Falls du aber schon weisst, dass die Implementierungen sowieso nie einen State haben, und es dir nur um die Funktionspointer aus der vtable geht, kannst du auch einfach fuer jede Implementierung ein globales Singleton bereit stellen und dann (ohne dynamische Allokationen!) mit einfachen Pointern auf diese arbeiten.

In folgendem Beispiel habe ich mal beides beispielhaft implementiert (braucht wegen std::make_unique C++14):

Code:
#include <iostream>
#include <stdexcept>
#include <memory>

class IF {
public:
  // template method interfaces
  void ident() const {
  	 ident_impl();
  }
  virtual ~IF() {}
private:
  // template method implementations
  virtual void ident_impl() const = 0;
};

class ImplA : public IF {
   virtual void ident_impl() const override {
      std::cout << "This is A\n";
   }
};

class ImplB : public IF {
   virtual void ident_impl() const override {
      std::cout << "This is B\n";
   }
};

class ImplC : public IF {
   virtual void ident_impl() const override {
      std::cout << "This is C\n";
   }
};

//! Used to identify all available implementations
enum ImplType {
	i_a, i_b, i_c
};

//! Maps \c ImplType to its associated implemention of \c If
template <ImplType t>
struct ToType {};
template <> struct ToType<i_a> { typedef ImplA type; };
template <> struct ToType<i_b> { typedef ImplB type; };
template <> struct ToType<i_c> { typedef ImplC type; };

typedef std::unique_ptr<IF> IFUniquePtr;
typedef IF* IFPtr;

template <ImplType t>
IFUniquePtr construct_if()
{
   return std::make_unique<typename ToType<t>::type>();
}

static IFUniquePtr construct_if(ImplType type) {
	switch (type) {
		case i_a: return construct_if<i_a>();
		case i_b: return construct_if<i_b>();
		case i_c: return construct_if<i_c>();
		default: throw std::runtime_error("Invalid ImplType");
	}
}

template <ImplType t>
IFPtr get_if_singleton()
{
	static typename ToType<t>::type singleton;
	return &singleton;
}

static IFPtr get_if_singleton(ImplType type) {
	switch (type) {
		case i_a: return get_if_singleton<i_a>();
		case i_b: return get_if_singleton<i_b>();
		case i_c: return get_if_singleton<i_c>();
		default: throw std::runtime_error("Invalid ImplType");
	}
}

static void test_unique() {
	std::cout << "Testing unique...\n";
	IFUniquePtr first = construct_if(i_a);
	IFUniquePtr second = construct_if(i_b);
	first->ident();
	second->ident();
	std::swap(first, second);
	first->ident();
	second->ident();
	first = construct_if(i_c);
	first->ident();
}

static void test_singleton() {
	std::cout << "Testing singleton...\n";
	IFPtr first = get_if_singleton(i_a);
	first->ident();
	first = get_if_singleton(i_b);
	first->ident();
	IFPtr second = get_if_singleton(i_c);
	first = second;
	first->ident();
	second->ident();
}

int main() {
	test_unique();
	test_singleton();
	return 0;
}
 
Zuletzt bearbeitet:
Zurück
Oben