Warum funktioniert folgender C++ Code?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Fitim
Beiträge: 12
Registriert: 27.05.2012, 19:29

Warum funktioniert folgender C++ Code?

Beitrag von Fitim »

Hallo zusammen

Wann immer ich als Hobbyprogrammierer ein wenig Zeit finde, stöbere ich durch die interessante Welt von C++ und lerne ständig neue interessante Konstrukte.
Nun bin ich auf folgendes Konstrukt gestossen, welches virtuelle Methoden simuliert. Nur, warum funktioniert das? Zuerst einmal der Code:

Code: Alles auswählen

#include <iostream>
#include <ostream>

namespace ConsoleApplication
{
	template <typename Derived>
	class Test
	{
	public:
		Test() : derived(static_cast<Derived *>(this)) {}

		void CallMethods()
		{
			derived->FirstMethod();
			derived->SecondMethod();
		}

		void FirstMethod() const
		{
			::std::cout << "Test::FirstMethod()" << ::std::endl;
		}

		void SecondMethod() const
		{
			::std::cout << "Test::SecondMethod()" << ::std::endl;
		}

	private:
		Derived * derived;
	};

	class Demo : public Test<Demo>
	{
	public:
		Demo() {}

		void FirstMethod() const
		{
			::std::cout << "Demo::FirstMethod()" << ::std::endl;
		}

		/*
		void SecondMethod() const
		{
			::std::cout << "Demo::SecondMethod()" << ::std::endl;
		}
		*/
	};
}

int main()
{
	using namespace ::ConsoleApplication;

	Test<Demo> test;
	test.CallMethods();

	return 0;
}
Test::CallMethods() ruft die bestehende Demo::FirstMethod() Methode auf, und danach die eigene Test::SecondMethod(). Doch warum klappt das?
Es wird ja nirgendwo ein Demo-Objekt erzeugt oder irre ich mich da? Es muss dazu gesagt werden, dass ich sehr wenig Erfahrung mit Templates habe.

Liebe Grüsse
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Warum funktioniert folgender C++ Code?

Beitrag von Krishty »

Klappt in C++03 nur durch Glück, weil – wie du gesagt hast – nirgends tatsächlich eine Demo-Instanz angelegt wird. Normalerweise verhindert man diesen Missbrauch, indem Tests K’toren protected gemacht werden. Du könntest es dann also nur durch eine abgeleitete Klasse instanzieren; d.h., indem du eine Demo-Instanz anlegst.

Für C++11 kenne ich die POD-Definition nicht gut genug um zu sagen, ob es standardkonform ist. Der Gedankengang ist: So lange die Klasse POD ist, darfst du so viel casten wie du willst, und es bleibt wohldefiniert. Für C++11 wurde die POD-Definition derart erweitert, dass auch Vererbung zugelassen ist. Das müsste ich also im Standard nachschauen; aber im Allgemeinen ist das, was du da zeigst, nicht erwünscht und, wie gesagt, in C++03 definitiv falsch.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Fitim
Beiträge: 12
Registriert: 27.05.2012, 19:29

Re: Warum funktioniert folgender C++ Code?

Beitrag von Fitim »

Ahh, Danke.

Und wieder was gelernt :-)
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Warum funktioniert folgender C++ Code?

Beitrag von dot »

Ich bin mir ziemlich sicher, dass das undefiniert ist, das es imo spätestens die strict aliasing rule verletzt. Abgesehen davon ist mir nicht ganz klar, inwiefern das "virtuelle Methoden simulieren" soll... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Warum funktioniert folgender C++ Code?

Beitrag von Krishty »

Wo werden die verletzt, abgesehen davon, dass die Demo-Instanz nicht existiert? Demo erbt von Test<Demo>, also muss jede Instanz von Test<Demo> als mit jeder Demo-Instanz überlappend angenommen werden, sofern das nicht explizit durch weitere Analyse ausgeschlossen werden kann.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Warum funktioniert folgender C++ Code?

Beitrag von dot »

Imo wird hier

Code: Alles auswählen

derived->FirstMethod();
auf ein Objekt über ein lvalue, das nicht seinem dynamischen Typ entspricht, zugegriffen.
simbad
Establishment
Beiträge: 130
Registriert: 14.12.2011, 14:30

Re: Warum funktioniert folgender C++ Code?

Beitrag von simbad »

Das wird solange funktionieren, solange nicht auf Attribute zugegriffen wird, die die Existenz einer Instanz verlangen. So einen Müll habe ich schonmal in sicherheitsrelevanter Software gefunden. die haben dreist mit einem null-pointer auf Methoden zugegriffen. Das ging, eben aus oben genannten Grund.
Auf meinen Hinweis, das die Methode vielleicht besser als Funktion aufgehoben wäre, weil die ja eh nix mit dem Objekt zutun hätte, hat man meinen Vorschlag abgelehnt und die Methode static deklariert. Was das nun besser gemacht hat war mir nicht klar.
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Warum funktioniert folgender C++ Code?

Beitrag von eXile »

simbad hat geschrieben:Das wird solange funktionieren, solange nicht auf Attribute zugegriffen wird, die die Existenz einer Instanz verlangen. So einen Müll habe ich schonmal in sicherheitsrelevanter Software gefunden. die haben dreist mit einem null-pointer auf Methoden zugegriffen. Das ging, eben aus oben genannten Grund.
Ich wollte noch etwas nur peripher Relatiertes hinzufügen: Auch in zur Kompilierzeit evaluierten Kontexten muss man mit C++11 keine static_cast<MyStruct *>(0)->… mehr schreiben, sondern kann declval (welches wohl via add_rvalue_reference implementiert werden kann) benutzen:

Code: Alles auswählen

#include <iostream>
#include <typeinfo>
#include <utility>

struct MyStruct
{
    int MyMethod(char);
};

int main(int argc, char * argv[])
{
    // What does MyStruct::MyMethod return?
    std::cout << typeid(decltype(std::declval<MyStruct *>()->
                                 MyMethod(std::declval<char>()))).name() << std::endl;
   
    return 0;
}
(Natürlich gab es auch vorher, da ja alles in so einem Fall zur Kompilierzeit stattfand, keine Sicherheitsprobleme, und es gibt keine Polymorphie. Aber vielleicht kannte jemand hier obigen Trick noch nicht, und ich wollte ihn mal zeigen.)
Antworten