Eines der Probleme von pimpl ist die Leistungsbeeinträchtigung bei der Verwendung (zusätzliche Speicherzuweisung, nicht zusammenhängende Datenelemente, zusätzliche Indirektionen usw.). Ich möchte eine Variation der Pimpl-Sprache vorschlagen, mit der diese Leistungseinbußen vermieden werden, wenn nicht alle Vorteile von Pimpl genutzt werden. Die Idee ist, alle privaten Datenelemente in der Klasse selbst zu belassen und nur die privaten Methoden in die pimpl-Klasse zu verschieben. Der Vorteil gegenüber Pimpl besteht darin, dass der Speicher zusammenhängend bleibt (keine zusätzliche Indirektion). Die Vorteile gegenüber der Nichtverwendung von Pimpl sind:
- Es verbirgt die privaten Funktionen.
- Sie können es so strukturieren, dass alle diese Funktionen eine interne Verknüpfung haben und der Compiler es aggressiver optimieren kann.
Meine Idee ist es also, den Pickel von der Klasse selbst erben zu lassen (klingt ein bisschen verrückt, ich weiß, aber ertrage es mit mir). Es würde ungefähr so aussehen:
In der Ah-Datei:
class A
{
A();
void DoSomething();
protected: //All private stuff have to be protected now
int mData1;
int mData2;
//Not even a mention of a PImpl in the header file :)
};
In der A.cpp-Datei:
#define PCALL (static_cast<PImpl*>(this))
namespace //anonymous - guarantees internal linkage
{
struct PImpl : public A
{
static_assert(sizeof(PImpl) == sizeof(A),
"Adding data members to PImpl - not allowed!");
void DoSomething1();
void DoSomething2();
//No data members, just functions!
};
void PImpl::DoSomething1()
{
mData1 = bar(mData2); //No Problem: PImpl sees A's members as it's own
DoSomething2();
}
void PImpl::DoSomething2()
{
mData2 = baz();
}
}
A::A(){}
void A::DoSomething()
{
mData2 = foo();
PCALL->DoSomething1(); //No additional indirection, everything can be completely inlined
}
Soweit ich sehe, gibt es absolut keine Leistungseinbußen bei der Verwendung von vs no pimpl und einige mögliche Leistungssteigerungen und eine sauberere Header-Dateischnittstelle. Ein Nachteil gegenüber Standard-Pimpl ist, dass Sie die Datenelemente nicht ausblenden können, sodass Änderungen an diesen Datenelementen immer noch eine Neukompilierung von allem auslösen, was von der Header-Datei abhängt. Aber so wie ich es sehe, ist es entweder der Vorteil oder der Leistungsvorteil, wenn die Mitglieder zusammenhängend im Speicher sind (oder machen Sie diesen Hack- "Warum Versuch Nr. 3 bedauerlich ist"). Eine weitere Einschränkung ist, dass wenn A eine Vorlagenklasse ist, die Syntax ärgerlich wird (Sie wissen, Sie können mData1 nicht direkt verwenden, Sie müssen dies tun-> mData1, und Sie müssen den Typnamen und möglicherweise Vorlagenschlüsselwörter für abhängige Typen verwenden und Schablonentypen usw.). Eine weitere Einschränkung ist, dass Sie in der ursprünglichen Klasse nicht mehr private, sondern nur geschützte Mitglieder verwenden können, sodass Sie den Zugriff von keiner ererbenden Klasse, nicht nur von der Zuhälterin, einschränken können. Ich habe es versucht, konnte dieses Problem aber nicht umgehen. Ich habe beispielsweise versucht, den Pimpl zu einer Friend-Template-Klasse zu machen, in der Hoffnung, die Friend-Deklaration so breit zu gestalten, dass ich die eigentliche Pimpl-Klasse in einem anonymen Namespace definieren kann, aber das funktioniert einfach nicht. Wenn jemand eine Idee hat, wie er die Datenelemente privat halten und dennoch einer in einem anonymen Namensraum definierten ererbenden Pimpl-Klasse den Zugriff auf diese erlauben kann, würde ich es wirklich gerne sehen! Das würde meine Hauptreservierung davon abhalten, dies zu verwenden.
Ich bin jedoch der Meinung, dass diese Vorbehalte für die Vorteile meines Vorschlags akzeptabel sind.
Ich habe versucht, online nach Hinweisen auf diese "Nur-Funktion-Zuhälter" -Sprache zu suchen, konnte aber nichts finden. Ich bin wirklich interessiert daran, was die Leute darüber denken. Gibt es andere Probleme oder Gründe, warum ich das nicht verwenden sollte?
AKTUALISIEREN:
Ich habe diesen Vorschlag gefunden , der mehr oder weniger versucht, genau das zu erreichen, was ich bin, aber dies durch Ändern des Standards. Ich stimme diesem Vorschlag voll und ganz zu und hoffe, dass er in den Standard aufgenommen wird (ich weiß nichts über diesen Prozess, daher habe ich keine Ahnung, wie wahrscheinlich dies sein wird). Ich hätte es viel lieber, wenn dies über einen eingebauten Sprachmechanismus möglich wäre. Der Vorschlag erklärt auch die Vorteile dessen, was ich viel besser erreichen möchte als ich. Es hat auch nicht das Problem, die Kapselung zu brechen, wie es mein Vorschlag getan hat (privat -> geschützt). Bis dieser Vorschlag es in den Standard schafft (falls dies jemals passiert), denke ich, dass mein Vorschlag es ermöglicht, diese Vorteile mit den von mir aufgeführten Vorbehalten zu erzielen.
UPDATE2:
In einer der Antworten wird LTO als mögliche Alternative erwähnt, um einige der Vorteile zu nutzen (ich vermute, aggressivere Optimierungen). Ich bin mir nicht sicher, was genau in verschiedenen Compiler-Optimierungsdurchläufen vor sich geht, aber ich habe ein wenig Erfahrung mit dem resultierenden Code (ich verwende gcc). Durch einfaches Einfügen der privaten Methoden in die ursprüngliche Klasse werden diese zu einer externen Verknüpfung gezwungen.
Ich könnte mich hier irren, aber die Art und Weise, wie ich das interpretiere, ist, dass der Optimierer zur Kompilierungszeit die Funktion nicht eliminieren kann, selbst wenn alle seine Aufrufinstanzen vollständig in dieser TU eingebettet sind. Aus irgendeinem Grund weigert sich sogar LTO, die Funktionsdefinition loszuwerden, selbst wenn es den Anschein hat, dass alle Aufrufinstanzen in der gesamten verknüpften Binärdatei inline sind. Ich habe einige Referenzen gefunden, die besagen, dass der Linker nicht weiß, ob Sie die Funktion irgendwie immer noch mithilfe von Funktionszeigern aufrufen (obwohl ich nicht verstehe, warum der Linker nicht herausfinden kann, dass die Adresse dieser Methode niemals verwendet wird ).
Dies ist nicht der Fall, wenn Sie meinen Vorschlag verwenden und diese privaten Methoden in einem Pickel in einem anonymen Namespace ablegen. Wenn diese inline werden, werden die Funktionen NICHT in der Objektdatei angezeigt (mit -O3, einschließlich -finline-Funktionen).
So wie ich es verstehe, berücksichtigt der Optimierer bei der Entscheidung, ob eine Funktion eingebunden werden soll oder nicht, ihre Auswirkungen auf die Codegröße. Mit meinem Vorschlag mache ich es für den Optimierer etwas "billiger", diese privaten Methoden zu integrieren.
PCALL
ist undefiniertes Verhalten. Sie können einA
in aPImpl
umwandeln und verwenden, es sei denn, das zugrunde liegende Objekt ist tatsächlich vom TypPImpl
. Sofern ich mich nicht irre, erstellen Benutzer nur Objekte vom TypA
.