Effiziente Polymorphie

Reduzierst du 'direct' darauf, dass eine Methode nicht weiter überschrieben werden kann musst du das nicht mit einem neuen Keyword machen, dieses Keyword gibts es schon und es nennt sich 'final'.
Nehmen wir das mal als Ansatz: Was nützt final dem Compiler oder der Laufzeit?

Wir machen es mal Schrittweise:
[(1)]Ob von einer Kindklasse geerbt wird oder nicht ist doch nicht weiter relevant. [(2)] Fakt ist, dass jemand der die Basisklasse kennt die Kindklasse nicht kennen muss.
Zu (1): Wenn von einer Kindklasse im Rang n geerbt wird, ist die Kindklasse die Basisklasse von der neuen Kindklasse im Rang n+1.
Entsprechend gilt mit (2): Wenn man weiss, dass eine Kindklasse im Rang n final ist, dann gibt es keine neue Kindklasse im Rang n+1.
Einverstanden?
Damit ist die Klassenvererbung quasi beschränkt, also [k; n]. Der Compiler kennt aber alle Klassen von k bis n (Ansonsten könntest du keine Kindklasse davon erstellen).
Einverstanden?
Damit löst final das Problem in Richtung n+1. Das Problem in Richtung n-1 ist aber nicht gelöst, sprich die (Base* b = Derived* d). Es kann nämlich sein, dass (wie in deinem Beispiel vor einigen Posts) eine Bibliothek nur die Basisklasse exportiert. Der Compiler, der dann die Bibliothek in sein Programm linkt (Ja, eigentlich macht es der Linker...) kann dann die Definition der Kindklasse(n) nicht kennen.
Einverstanden?
Entsprechend ist es nötig, eine VMT zu benutzen, da der Compiler nicht sicher wissen kann, ob eine Klasse nach aussen gegeben wird oder nicht, und Resultate aufgrund von Vermutungen sind in der Informatik nicht wirklich erwünscht. (Der Compiler soll nichts auf eigene Faust machen).
Einverstanden?
Nun käme die Idee ins Spiel: Der Programmierer sagt dem Compiler: "Hey, ich brauche diese Klasse nur intern und du kennst immer alle Basis- und Kindklassen". (Das ist anders als final, wo der Compiler ausschliesslich das Ende eines Astes einer Baumstruktur kennt. Hier würde er wissen, dass der Compiler den Baum von der Wurzel bis zum Ast kennt und der Baum nicht weiter wächst (auch nicht in Nachbars Garten)).
Einverstanden?

Das direct Keyword würde dann nur Sinn machen, wenn es auf Base angewandt wird, weil dann der Compiler weiss, dass er nach einem Symbol Base::func suchen muss und einen Aufruf darauf verwenden kann und nicht irgendwelche virtuellen Funktionen aufrufen muss. Das würde aber wiederum bedeuten, dass Base gar keine virtuellen Methoden haben muss, da es ja sowieso nicht vererbt werden kann.
Wird es ja quasi. Der Punkt ist ja gerade, eine statische Klasse aus (per Default) virtuellen zu erzeugen. Nur nicht von der Basisklasse, sondern von irgendeiner Klasse innerhalb des definierten Bereichs.

foo in meinem Beispiel kann nur dann einmal für VMTD und einmal für NoVMTD templateisiert werde, wenn zum Zeitpunkt des Aufrufs der ursprüngliche Code von foo noch bekannt ist. Das ist nur dann der Fall, wenn foo im gleichen Translationunit verarbeitet wird wie der Aufruf an foo. Wenn dem so ist brauchst du das Keyword auch nicht, weil der Compiler/Optimierer dann selber so schlau sein kann das einzubauen, wenn ihm danach ist.
Tut er ja auch. Allerdings nur bei Stackinstanzen. Nicht bei Pointern.

Damit sie das ist müsste sie, wie du erwähntest, sozusagen als template markiert werden, damit sie nicht in Objektcode sondern in eine ersetzbare Schablone umgewandelt wird. Dazu brauchen wir aber auch kein neues Keyword, dazu kannst du schlicht template verwenden.
Jetzt aber :)
C++:
char* ptr = "text";
ptr[3] //brauche ich nicht
(*(ptr + 3)) //Geht ja auch
Ein bisschen syntactic sugar ist immer ganz nett :)

Der Compiler/Optimierer kann nun bei jedem Aufruf entscheiden welche Variante er nehmen möchte und wie er das umsetzen will, da es sich ja nur um ein template handelt.
Ja.

Nein, dann müsstest du deinem keyword noch weitere Eingeschaften hinzufügen. So müsste zum Beispiel Derived von Base immer private erben. Also Derived ist dann kein Base mehr, kann auch nicht mehr in ein Base umgewandelt werden und hat überhaupt keine Relation mehr zu Base, ausser dass es halt die Methoden und Felder einfach übernimmt.
Ok, ich habe mich ein bisschen missverständlich ausgedrückt: Eine Klasse, die polymorph sein kann, aber von der Art, wie Funktionen aufgerufen werden (und damit der Performance) nicht von einer statischen Klasse zu unterscheiden ist.
Wie erwähnt ist das weitervererben nicht der riskante Punkt davon.
Da hast du Recht. Das Weitergeben, ohne die Informationen für die Basisklassen mitzugeben jedoch schon.

Du kannst bei der Klasse nicht entscheiden ob eine VMT nötig ist oder nicht, das entscheidet die Verwendung. Den Zusammenhang zu new versteh ich nicht. Es ist im Standard klar definiert, was passiert, wenn eine Exception nicht abfangen wird und ein nullptr ist auch nichts undefiniertes.
Nun, da ich die Klasse verwende, kann ich sehr wohl entscheiden, ob eine VMT nötig ist. Natürlich ist die Verwendung ausschlaggebend. Aber wenn ich "die Verwendung bin", dann geht das schon.
Zu new: Ja. Man kann ja auch in den Standard schreiben "direct bitte nur sehr bedacht oder Drachen passieren". Natürlich muss man es erst definieren. Aber der Sinn hier ist ja, eine mögliche Definition zu finden, um dem Compiler das zu ermöglichen.
In der realen Welt hat eine VMT keinen nennenswerten Einfluss auf die Laufzeit.
Ah, "nennenswert" ist subjektiv. Ist es im Vergleich zu Dateizugriffen vernachlässigbar? Sicherlich. Im Vergleich zu Heapallocations ebenfalls. Aber wenn eine Möglichkeit besteht, das zu optimieren (wenn auch nur in Spezialfällen), warum sollte man es nicht tun?
Klar, wenn du sagst, die VMT gehöre nicht in den Standard, dann verstehe ich das auch, zumindest teilweise. Aber das ist dann ja wieder eine Grundsatzfrage: Macht man einen Seitenzweig des Standards? Definiert man einfach das Keyword und lässt es versanden, wenn eine Möglichkeit gefunden wurde, sämtliche Polymorphie ohne VMT zu betreiben? Und so weiter und so fort.
Wo Mikrooptimierungen in diesem Ausmasse nötig sind sollte man gleich auf C umsteigen.
C ist eben nicht immer schneller als C++. C kennt inlining schlecht (zumindest Standard-C ohne C++), Callbacks sind die Regel. Klar kann man mischen - aber wieso sollte C++ nicht alles versuchen, um die Vorteile von C und jene von OOP zu vereinen?
Dort herrscht generell ja auch die Akzeptanz, dass dich jede Instruktion aus nicht-reproduzierbaren und nicht-debuggbaren Gründen ins Nirvana kicken kann.
Wot?
Äh. Ein Beispiel bitte. Und nicht irgendwas mit Verwendung von undefinierten Pointern. Also wenn man es hinkriegt, etwas nicht-reproduzierbar und nicht-debuggbar zu machen... Aber noch "Akzeptanz" dafür? "Hey, guck mal. Dein Bremssystem im Auto ist einfach abgeraucht" - "Tja, wurde halt in C geschrieben. Muss man akzeptieren."
Auch C ist nicht der Wild Wild West der Programmierung :)

Gruss
cwriter
 
Der Compiler kennt aber alle Klassen von k bis n (Ansonsten könntest du keine Kindklasse davon erstellen).
Einverstanden?

Grundsätzlich nicht einverstanden. Ein wesentlicher Aspekt von Polymorphie ist es eben, dass der Compiler nicht k bis n kennen muss. Es kann gut sein, wie bei foo, dass wenn foo übersetzt wird in Objektcode nur Base bekannt ist, aber die Kindklassen kennst du zum Zeitpunkt von foo nicht. Es bleibt das Beispiel von früher:
File 1: Base.h -> deklariert Base
File 2: Derived.h -> deklariert Derived als Kind von Base und als direct
File 6: Derived2.h -> deklariert Derived2 als Kind von Base und nicht direct
File 3: Foo.h:
#include "Base.h"

void foo(Base* base);

File 4: Foo.cpp:
#include "Foo.h"

void foo(Base* base) { base->func(); }

File 5:
#include "Derived.h"
#include "Foo.h"

int main() {
foo(new Derived());
}

Woher weiss die Implementation in Foo.cpp, dass sie aufpassen muss, weil base->func() nicht immer gleich aufgerufen werden muss?

Woher weiss File 5, wie es den Aufruf an foo gestalten soll, weil Derived ja jetzt ohne VMT arbeitet ohne aber den Code von foo zu kennen?

Du müsstest die ganze Kette Derived bis Base als direct markieren, weil keine davon ausserhalb der deklarierenden Cpp Datei verwendet werden kann. Man darf hier auch niemals vergessen, dass die ganze Projektstruktur in VS künstlich ist. Schlussendlich wird jede C++ Datei einzeln und unabhängig von allen anderen compiliert.

Nun käme die Idee ins Spiel: Der Programmierer sagt dem Compiler: "Hey, ich brauche diese Klasse nur intern und du kennst immer alle Basis- und Kindklassen". (Das ist anders als final, wo der Compiler ausschliesslich das Ende eines Astes einer Baumstruktur kennt. Hier würde er wissen, dass der Compiler den Baum von der Wurzel bis zum Ast kennt und der Baum nicht weiter wächst (auch nicht in Nachbars Garten)).
Einverstanden?

Wenn direct wirklich bedeutet, dass eine Klasse in keiner Form nach draussen gelangen kann ist die Frage wozu man diese Klasse generell überhaupt macht. Das würde dann bedeuten, dass die Klasse ausschliesslich in der C++ Datei verwendet wird, in der sie auch deklariert wird. Hier hast du dann halt wieder das Problem, dass du den Standard konzeptionell anders aufbauen musst. Du musst das Konzept verschiedener Translation Units integrieren und wie die im Zusammenhang stehen. Du müsstest klären, was es heisst "ausserhalb" verwendet zu werden um sagen zu können, was "nicht ausserhalb verwendet" bedeutet, usw.


Jetzt aber :)
C++:
char* ptr = "text";
ptr[3] //brauche ich nicht
(*(ptr + 3)) //Geht ja auch
Ein bisschen syntactic sugar ist immer ganz nett :)

Dein Beispiel ist wirklich nur Syntaxvereinfachung. template und direct sind jedoch grundsätzlich anders. Durch template wird eben nicht automatisch undefiniertes Verhalten induziert, durch direct hingegen schon.

Ah, "nennenswert" ist subjektiv. Ist es im Vergleich zu Dateizugriffen vernachlässigbar? Sicherlich. Im Vergleich zu Heapallocations ebenfalls. Aber wenn eine Möglichkeit besteht, das zu optimieren (wenn auch nur in Spezialfällen), warum sollte man es nicht tun?

Weil mit einer marginalen Performanceoptimierung massive Qualitätseinbussen kommen.
 
Grundsätzlich nicht einverstanden.
Ist das ein implizites "Einverstanden" für die anderen Punkte?

Ein wesentlicher Aspekt von Polymorphie ist es eben, dass der Compiler nicht k bis n kennen muss.
Darf/Kann er sie kennen?

Ich probiere mal, Präprozessor zu spielen:
C++:
//Andere Header

class Base{
   //Whatever
};

direct class Derived : public Base{
    //Whatever
};

void foo(Base* base);

int main() {
    foo(new Derived());
}
Das kriegt der Compiler (ich habe mal #pragma once angenommen und Verdoppelungen entfernt) zu futtern.
Einverstanden?

Nun tut sich tatsächlich ein kleines Problemchen auf: Zwar kennt der Compiler in der main.cpp alle Deklarationen und Klassendefinitionen, allerdings nicht die Funktionsdefinition von foo, sondern nur die Deklaration.

In diesem Fall ist anzunehmen, dass der Compiler foo (bzw. den ASM-Code davon) inline einbauen wird. Das wird wohl sowas sein wie
Code:
;ptr sei, was "new Derived()" zurückgibt (ja, stimmt nicht ganz)
mov ecx, ptr
call [[ecx]] ;Ich habe mal den Zwischenschritt "geinlinet"
In der Tat ist es so für den Optimizer eigentlich unmöglich, die Informationen rauszuziehen, die er nachträglich bräuchte.
Es wäre somit zumindest ein erneutes Kompilieren der relevanten Teile nötig.

Noch etwas kleines zu den Wörtern:
Ich verstand Deklaration immer als Hinweis, dass es etwas diesen Namens gibt.
Bsp: class B; //Sagt aus, dass es eine Klasse gibt
Hingegen die Definition als komplette Erklärung.
class B{} //Sagt, wie die Klasse aussieht
Analog ist "void func();" eine Deklaration und "void func(){}" die Definition.

Du scheinst aber zumindest class A : B {} als Deklaration aufzufassen, denn ansonsten würde dein Beispiel gar nicht kompiliert werden können. Gibt es bei Klassen eine andere Regel für die Nomenklatur?

Werden so viele Klassen in CPP-Dateien definiert? Ich setze sie immer in die Headerdateien, da so der Zugriff von verschiedenen cpp-Dateien leichter fällt. Gibt es dafür einen Grund (ausser, um keine internen Daten rauszugeben, wenn man eine CSS als lib verteilen will)?

Entsprechend auch:
Hier hast du dann halt wieder das Problem, dass du den Standard konzeptionell anders aufbauen musst. Du musst das Konzept verschiedener Translation Units integrieren und wie die im Zusammenhang stehen. Du müsstest klären, was es heisst "ausserhalb" verwendet zu werden um sagen zu können, was "nicht ausserhalb verwendet" bedeutet, usw.
"Ausserhalb" galt für mich als Library (also wenn der Sourcecode nicht mehr bekannt ist).

Aber ansonsten interessante Inputs. Ich setze sie mal auf meine bucket list und melde mich in einigen Dekaden wieder mit einer neuen Programmiersprache und neuen Konzepten. :)

Ich denke, es wird Zeit, die Diskussion um "direct"/VMT/virtual zu beenden, auch wenn sie sehr lehrreich war. Ich hoffe, nicht zu viele Nervenstränge auf den Gewissen haben zu müssen und bedanke mich.

Gruss
cwriter


Und es ginge doch. :D

Irgendwie.:confused: Krieg ich einen Keks, wenn ich wider Erwarten eine neue Sprache und einen Compiler dazu schreibe?:cool:
 
Wenn die neue Sprache auch keine groben Einschränkungen im Vergleich zum ganzen Modell von C++ hat
kannst du so viele Kekse haben, wie du willst, und den Turingpreis dazu :D
 
Zurück