[C++] Echte private Methoden und Implementierungen

Hier können Artikel, Tutorials, Bücherrezensionen, Dokumente aller Art, Texturen, Sprites, Sounds, Musik und Modelle zur Verfügung gestellt bzw. verlinkt werden.
Forumsregeln
Möglichst sinnvolle Präfixe oder die Themensymbole nutzen.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

[C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Ich bin nun seit geraumer Zeit auf der Suche nach einem einheitlichen Ansatz für echte private Methoden und private Implementierungen in C++. Das heißt, private Methoden und ggf. auch private Attribte sollen auf keinen Fall in der Klassendefinition auftauchen, um die öffentliche Schnittstelle nicht zu verunreinigen und insbesondere andere Module bei Änderungen nicht zu beeinflussen. Hier meine Ergebnisse:

Interfaces
Die einfachste Variante, die wohl auch weithin bekannt ist, ist das Ersetzen öffentlicher Klassendefinitionen durch rein virtuelle Schnittstellendefinitionen. Die am wenigsten redundante Implementierung sieht in diesem Fall Java-Modulen sehr ähnlich:

Code: Alles auswählen

// header (*.h)
class pimpl_interface
{
   // delete assignment operator
   pimpl_interface& operator =(const pimpl_interface&); // C++11: = delete;
public:
   virtual ~pimpl_interface() { }
};

class foo : public pimpl_interface
{
public:
   // public methods
   virtual void publicMethod() = 0;
};

std::unique_ptr<foo> createFoo(...);

// implementation (*.cpp)
class fooImpl : public foo
{
   // attributes
   int i;

   // private methods
   void privateMethod(int &i)
   {
      ++i;
   }

public:
   // public methods
   void publicMethod()
   {
       privateMethod(i);
       ++i;
   }
};

std::unique_ptr<foo> createFoo(...)
{
   return unique_ptr<foo>( new foo(...) );
}
Diese Variante ist einfach und effektiv: Sie hält sich an die gängige C++-Klassenstruktur und ermöglicht dabei gleichermaßen das Verstecken von privaten Methoden und Attributen.
Doch auch die Nachteile liegen auf der Hand: Alle Objekte landen im Freispeicher. Eine extra create-Funktion ist erforderlich, die Liste der Konstruktorparameter muss doppelt angegeben werden und in der create-Funktion müssen alle Parameter von Hand an den Konstruktor weitergegeben werden. Insbesondere helfen hier auch in C++11 keine Variadic Templates, weil die create-Funktion nicht im Header definiert werden kann.
Obendrein sind alle öffentlichen Methoden virtuell und haben somit einen (wenn auch sehr sehr geringen, also in der Regel zu vernachlässigenden) Mehraufwand. Entscheidend ist hier, dass der Compiler Methoden ggf. zur Link-Zeit nicht inlinen kann, die er ohne die Indirektion beim virtuellen Aufruf durchaus inlinen könnte. Nebenbei ist hier Erweiterung durch Vererbung kaum sinnvoll möglich.

Private Methoden
In vielen Fällen reicht es aus, sich klar zu werden, dass private Funktionalität überhaupt nicht in die Klasse muss, sondern ganz einfach in modulinternen Funktionen implementiert werden kann:

Code: Alles auswählen

// header (*.h)
class foo
{
public:
    // public methods
   void publicMethod();

private:
   int i;
};

// implementation (*.cpp)
namespace
{

// private functions ("methods")
void privateMethod(int &i)
{
    ++i;
}

} // namespace

// public methods
void foo::publicMethod()
{
    privateMethod(i);
    ++i;
}
Private Funktionen ("Methoden") dabei in einem anonymen Namespace zu definieren, ist äußerst wichtig, weil es sonst zu Konflikten beim Linken kommt, sobald mehrere Module zufällig private Funktionen mit derselben Signatur definieren. Ein Problem bei dieser Form von privaten Funktionen ist, dass private typedefs in den privaten Funktionen nicht genutzt werden können, weil diese formal nicht zur jeweiligen Klasse gehören. Fälle, in denen typedefs nicht öffentlich zugänglich sein dürfen, sind jedoch meiner Erfahrung nach äußerst selten.

Diese erste gezeigte Form der Umwandlung privater Methoden in private Funktionen ist sowohl einfach als auch sauber. Insbesondere wird durch die explizite Übergabe privater Attribute auch auf Aufruferseite deutlich, welche Attribute die jeweilige private Funktion tatsächlich verändert (keine versteckten Nebenwirkungen). Ein Teil der privaten Funktionen ist dank der vollständigen Entkopplung von der jeweiligen Klasse möglicherweise sogar außerhalb des Moduls nützlich, und kann somit in Bibliotheksfunktionen übergehen.

Leider gibt es Fälle, in denen die Einzelübergabe privater Attribute an private Funktionen alleine aufgrund der großen Attributanzahl äußerst mühsam und damit unpraktikabel wird. In diesem Fall kann auf eine verschachtelte Attributstruktur zurückgegriffen werden, deren Typ öffentlich zugänglich, die Instanz selbst jedoch privat ist:

Code: Alles auswählen

// header (*.h)
class foo
{
public:
   foo(...);

   // public methods
   void publicMethod();

   struct M
   {
      // attributes
      int sth;
      int sthElse;

      M(...);
   };

private:
   M m;
};

// implementation (*.cpp)
foo::M::M(...)
   : sth(0),
   sthElse(1)
{
   // consistent member access
   M &m = *this;
   m.sth = ...;
   // ...
}

foo::foo(...)
   : m(...)
{
}

// private methods
void privateMethod(foo::M &m)
{
    m.sth = ...;
}

// public methods
void foo::publicMethod()
{
    privateMethod(m);
    m.sthElse = ...;
}
Ein nebensächlicher Vorteil dieser Variante ist, dass auf Attribut-Präfixe der Art m oder m_ vollständig verzichtet werden kann, weil sich das Präfix ganz natürlich aus dem Zugriff auf die verschachtelte Attributstruktur ergibt. Dabei erhält man mit m. im Gegensatz zu m_ sogar immer direkt IntelliSense-Vorschläge.
Ein anonymer namespace ist hier nicht zwingend erforderlich, weil die Signatur durch den Parametertyp foo::M stets eindeutig einer Klasse und damit einem implementierenden Modul konfliktfrei zuzuordnen ist.

Ein unschöner Nachteil der zweiten gezeigten Variante ist, dass die Konstruktorparameterliste wie bei Interfaces (siehe oben) zweimal angegeben werden muss, hier einmal für foo und einmal für foo::M. Zudem müssen auch hier die Parameter im Konstruktor von foo von Hand an foo::M weitergegeben werden. Mit C++11 lässt sich dieses Problem lösen, in MSVC++ müssen wir aber leider noch auf die Unterstützung von Variadic Templates warten:

Code: Alles auswählen

// header (*.h)
class foo
{
public:
   template<typename ...Args>
   foo(Args&& ...args) : m(std::forward<Args>(args)...) { }

   // public methods
   void publicMethod();

   struct M
   {
      // attributes
      int sth;
      int sthElse;

      M(...);
   };

private:
   M m;
};

// implementation (*.cpp)
foo::M::M(...)
   : sth(0),
   sthElse(1)
{
   // consistent member access
   M &m = *this;
   m.sth = ...;
   // ...
}

// private methods
void privateMethod(foo::M &m)
{
    m.sth = ...;
}

// public methods
void foo::publicMethod()
{
    privateMethod(m);
    m.sthElse = ...;
}
Eine schöne Alternative, die kein Verpacken der Attribute erfordert, kam im Verlauf dieses Threads auf. Diese Methode dürfte programmiertechnisch den geringsten Zusatzaufwand haben und damit die Produktivität (auch gegenüber klassischen privaten Methoden) maximal steigern:

Code: Alles auswählen

// header (*.h)
class foo
{
public:
   // public methods
   void publicMethod();

private:
    // attributes
    int sth;
    int sthElse;

    struct M;
};

// implementation (*.cpp)
struct foo::M
{
    // private methods
    static void privateMethod(foo &m)
    {
        m.sth = ...;
    }
};

// public methods
void foo::publicMethod()
{
    M::privateMethod(*this);
    sthElse = ...;
}
Private Implementierungen
Manchmal sollen nicht nur private Methoden, sondern auch private Attribute nach Außen nicht sichtbar sein. (Eigentlich fast immer, schon um Abhängigkeiten zu anderen Modulen zu vermeiden. Leider lassen sich private Attribute praktisch nicht elegant ohne Overhead verstecken, weswegen am Ende eine Abwägung zwischen Vermeidung von Abhängigkeiten und Vermeidung von Overhead steht.)

Etwas besseres als das PImpl-Muster (neben Interfaces, siehe oben) habe ich hier auch nicht finden können:

Code: Alles auswählen

// header (*.h)
class foo
{
public:
   foo(...);
   ~foo();
   // public methods
   void publicMethod();

   struct M;
private:
   std::unique_ptr<M> m; // bonus: use wrapper to enforce initialization, see http://herbsutter.com/gotw/_101/
};

// implementation (*.cpp)
struct foo::M
{
     // attributes
     int sth;
     int sthElse;
   
     M(...)
        : sth(0),
        sthElse(1)
    {
        // consistent member access
        M &m = *this;
        m.sth = ...;
        // ...
    }
};

foo::foo(...)
   : m( new M(...) )
{
}

// required, foo::M incomplete outside implementation file
foo::~foo()
{
}

// private methods
void privateMethod(foo::M &m)
{
    m.sth = ...;
}

// public methods
void foo::publicMethod()
{
    privateMethod(*m);
    m->sthElse = ...;
}
// alternative: consistent member access
void foo::publicMethod()
{
    M &m = *this;
    privateMethod(m);
    m.sthElse = ...;
}
Ich habe das Muster leicht angepasst, um einheitlich mit den Overhead-losen Ansätzen privater Funktionen zu bleiben. Insbesondere lege ich in M auch in diesem Fall praktisch nur Daten ab, weil ich mich daran gewöhnt habe, private Methoden gar nicht mehr in der Klasse zu deklarieren, sondern einfach nur noch außerhalb als private Funktionen zu definieren. Damit sinkt insbesondere die Hürde, neue private Funktionen einzuführen, was sowohl der Produktivität als auch der Strukturierung sehr zuträglich sein kann.

Übrigens hat M &m = *this; nicht nur Vorteile in Bezug auf Einheitlichkeit (d.h. keine Veränderung des Codes bei Auslagerung in private Funktionen), sondern auch in Bezug auf Effizienz: Da sich lokale Referenzen nie ändern, kann der Compiler auch nach dem Aufruf anderer Funktionen über das lokale m referenzierte Attribute weiterhin direkt dereferenzieren, ohne noch einmal die Adresse von m aus this->m lesen zu müssen. Damit ist der Zugriff auf PImpl-Attribute genauso effizient wie der Zugriff auf Attribute über this.

Um die Zahl der Fehlerquellen zu minimieren und das PImpl-Muster weitestgehend zu automatisieren, empfiehlt sich ein entsprechender Wrapper. Herb Sutters PImpl-Wrapper ist hierfür der richtige Ausgangspunkt.

Das PImpl-Muster teilt sich einige Nachteile mit der eingangs vorgestellten Interface-Variante: Zwar ist die Benutzung der PImpl-Klasse von außen natürlicher, weil die Standard-Objektkonstruktions- und Destruktionsmechanismen, insbesondere automatische Lebenszeit ohne Zeiger-Wrapper, genutzt werden können. Intern landet das Objekt jedoch in beiden Fällen schlussendlich im Freispeicher, die PImpl-Klasse hat die Verwaltung des entsprechenden Objektes lediglich bereits mit eingebaut. Auch die Redundanz bei der Konstruktion wird man intern nicht los, neben den öffentlichen Konstruktoren ist meist mindestens ein zusätzlicher interner Konstruktor der privaten Implementierung notwendig (es sei denn, man verzichtet auf Initialisierung in der Initialisierungsliste).

Vorteile der PImpl-Variante: Im Gegensatz zur Interface-Variante erlaubt die PImpl-Variante Erweiterung durch Vererbung. Zudem sind öffentliche Methoden nicht virtuell. Damit verschwinden zum einen Indirektionen beim Aufruf, viel wichtiger ist jedoch, dass der Compiler in diesem Fall zur Link-Zeit Methoden inlinen kann.

Fazit
So weit einige Skizzen zu den Implementierungsmustern, die ich heute verwende, um die Verfehlungen von C++ in Bezug auf das Modulsystem zu lindern. Schön ist, dass einige dieser Muster, insbesondere private Funktionen, sogar die Produktivität steigern können. Andere Muster, wie das PImpl-Muster, machen das Programmieren unweigerlich mindestens ein bisschen umständlicher, ich hoffe aber, dass ich mit den hier vorgestellten Varianten diese Umstände bestmöglich minimieren konnte.

Nachtrag 1: Konstrastierung von Interfaces und PImpl-Muster.

Nachtrag 2: Private Funktionen mit Nebenwirkungen ohne Verpacken der Attribute möglich, entsprechend nachgetragen.
Zuletzt geändert von CodingCat am 04.07.2012, 14:09, insgesamt 3-mal geändert.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von Artificial Mind »

Nur eine kurze Frage:
Statt die privaten Funktionen in einen anonymen Namespace zu packen, kann man doch einfach "static" davorschreiben, oder?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Ja. static zu diesem Zweck war eine Zeit lang deprecated, im endgültigen C++11-Standard ist es aber wohl wieder gleichberechtigt mit anonymen Namespaces.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von Artificial Mind »

Ah ok, ich wusste gar nicht, dass das deprecated war, gabs dafür einen Grund?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Man hielt (und hält?) unbenannte Namespaces wohl für die bessere Alternative, weil sie sich besser ins Gesamtkonzept einfügen und dem static-Schlüsselwort die Doppeldeutigkeit nehmen. Es gibt einige Fälle, in denen das static-Schlüsselwort nicht weiterhilft, insbesondere bei der Definition modulinterner Klassen:

Code: Alles auswählen

namespace
{
   class internal_class
   {
      void foo();
   };

   void internal_class::foo()
   {
   }
}
Innerhalb von Klassen hat static eine andere Bedeutung, deshalb kann foo() nicht einfach static deklariert werden, um Konflikte beim Linken mit anderen Modulen zu vermeiden. Unbenannte Namensräume decken dagegen alle Fälle einheitlich ab.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von Artificial Mind »

Ah ok, danke. Ja, das macht Sinn.
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von eXile »

CodingCat hat geschrieben:weil ich mich daran gewöhnt habe, private Methoden gar nicht mehr in der Klasse zu deklarieren, sondern einfach nur noch außerhalb als private Funktionen zu definieren. Damit sinkt insbesondere die Hürde, neue private Funktionen einzuführen, was sowohl der Produktivität als auch der Strukturierung sehr zuträglich sein kann.
Kann das nicht sehr schnell dazu führen, dass die Interfaces nicht mehr minimal sind? Kurzes Beispiel:

Code: Alles auswählen

// .h
class myClass
{
public:
	explicit myClass(float theF, double theD, int theI) : f(theF), d(theD), i(theI) {};

	double CalculateWhatevers();
	double CalculateWhateversTimesTwo();

private:
	float f;
	double d;
	int i;
}

// .cpp
double myClass::CalculateWhatevers()
{
	return i * f * d;
}

double myClass::CalculateWhateversTimesTwo()
{
	return 2 * i * f * d;
}
Die Redudanz refactorn wir in einer privaten Methode weg:

Code: Alles auswählen

// .h
class myClass
{
public:
	explicit myClass(float theF, double theD, int theI) : f(theF), d(theD), i(theI) {};

	double CalculateWhatevers();
	double CalculateWhateversTimesTwo();

private:
	double CalculateIntermediateResult();

private:
	float f;
	double d;
	int i;
}

// .cpp
double myClass::CalculateWhatevers()
{
	return CalculateIntermediateResult();
}

double myClass::CalculateWhateversTimesTwo()
{
	return 2 * CalculateIntermediateResult();
}

double myClass::CalculateIntermediateResult()
{
	return i * f * d;
}
Dahingegen müsste man mit einer privaten Funktion weitere öffentliche Methoden definieren:

Code: Alles auswählen

// .h
class myClass
{
public:
	explicit myClass(float theF, double theD, int theI) : f(theF), d(theD), i(theI) {};
 
        double CalculateWhatevers();
        double CalculateWhateversTimesTwo();
 
        float GetF() { return f; };
        double GetD() { return d; };
        int GetI() { return i; };
 
private:
        float f;
        double d;
        int i;
};
 
// .cpp
namespace
{
double CalculateIntermediateResult(myClass & theClass)
{
        return theClass.GetF() * theClass.GetD() * theClass.GetI();
}
}
 
double myClass::CalculateWhatevers()
{
        return CalculateIntermediateResult(*this);
}
 
double myClass::CalculateWhateversTimesTwo()
{
        return 2 * CalculateIntermediateResult(*this);
}
Die Methoden GetF, GetD und GetI gehören aber nicht zum minimalen Interface und sollten eigentlich gar nicht existieren. Oder verstehe ich dich komplett falsch?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Ja, du verstehst mich komplett falsch. ;)

Der einzige Zweck dieser Ausführungen ist doch gerade, Varianten zu zeigen, die private Funktionalität verstecken, ohne dabei das Geheimnisprinzips aufzugeben. Zwei Möglichkeiten wären:

Code: Alles auswählen

// .h
class myClass
{
public:
	explicit myClass(float theF, double theD, int theI) : f(theF), d(theD), i(theI) { }
 
        double CalculateWhatevers();
        double CalculateWhateversTimesTwo();
 
private:
        float f;
        double d;
        int i;
};
 
// .cpp
namespace
{
double CalculateIntermediateResult(int f, int d, int i)
{
        return f * d * i;
}
}
 
double myClass::CalculateWhatevers()
{
        return CalculateIntermediateResult(f, d, i);
}
 
double myClass::CalculateWhateversTimesTwo()
{
        return 2 * CalculateIntermediateResult(f, d, i);
}
Komfort von privaten Methoden erhalten:

Code: Alles auswählen

// .h
class myClass
{
public:
	explicit myClass(float theF, double theD, int theI) : m{theF, theD, theI} { }
 
        double CalculateWhatevers();
        double CalculateWhateversTimesTwo();

        struct M
        {
                float f;
                double d;
                int i; 
        };
private:
        M m;
};
 
// .cpp
namespace
{
double CalculateIntermediateResult(const myClass::M &m)
{
        return m.f * m.d * m.i;
}
}
 
double myClass::CalculateWhatevers()
{
        return CalculateIntermediateResult(m);
}
 
double myClass::CalculateWhateversTimesTwo()
{
        return 2 * CalculateIntermediateResult(m);
}
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von eXile »

Mmmh stimmt. Da hast du recht. :)
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Du hast mich aber gerade auf eine dritte Variante gebracht, die eventuell angenehmer ist, weil sie keine Delegation bei der Konstruktion erfordert:

Code: Alles auswählen

// .h
class myClass
{
public:
        explicit myClass(float f, double d, int i) : f(f), d(d), i(i) { }
 
        double CalculateWhatevers();
        double CalculateWhateversTimesTwo();

private:
        float f;
        double d;
        int i;

        struct M;
};
 
// .cpp
struct myClass::M
{
        static double CalculateIntermediateResult(const myClass &m)
        {
                return m.f * m.d * m.i;
        }
};
 
double myClass::CalculateWhatevers()
{
        return M::CalculateIntermediateResult(*this);
}
 
double myClass::CalculateWhateversTimesTwo()
{
        return 2 * M::CalculateIntermediateResult(*this);
}
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von kimmi »

Zu demThema findet man auch viel Interessantes in dem Buch Large Scale C++ Design. Komplett private Methoden haben nämlich noch andere Vorteile:
  • Kein neues Bauen beim Anpassen der provaten Implementierungen.
  • Dadurch optimierte Compile-Zeiten
Gruß Kimmi
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Ja, genau das meine ich, wenn ich von Abhängigkeiten spreche, und ist wohl meine Hauptmotivation. :) Mir fällt gerade auf, dass ich zur Motivation praktisch nichts geschrieben habe, liegt wohl daran, dass der "Artikel" zunächst einmal eine Notiz an mich selbst war.

Interessant, dass es dazu ein Buch gibt, mal sehen, ob ich da mal reinschauen kann.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von kimmi »

Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Ich habe gerade Traits (z.B. in Scala) als Konzept zur expliziten Trennung der Wiederverwendung einer Implementierung durch Vererbung von Polymorphie/Subtyping durch Vererbung kennen gelernt. Das Konzept spricht mich insofern an, als dass es auf den ersten Blick eine Menge unnötiger Komplexität und Verschränkungen der klassischen Mehrfachvererbung eliminiert. So gibt es beispielsweise das Diamantenproblem (Rautenproblem) einfach nicht. Außerdem sind das Zuordnen einer Klasse zu einer Menge verhaltenskonformer Typen und die Wiederverwendung von entsprechendem Code zwei vollständig getrennte, explizite und somit leicht durchschaubare Vorgänge. Die Voraussetzung für diese Trennung ist, dass auch aus Traits in Form von Methoden importierte Funktionalität alle durch die importierende Klasse von Interfaces ererbten Methoden überschreiben kann:

Code: Alles auswählen

interface Entity { void setPosition(const vec3 &pos); ... }
interface Renderable { void render(Context &context) const; }

trait FreeEntity { vec3 pos; ... void setPosition(const vec3 &pos) { this->pos = pos; } ... }
trait MeshRenderable { MeshData data; void render(Context &context) const { use data ... } }

class SomeRenderableEntity implements Entity, Renderable with FreeEntity, MeshRenderable
{
   // Methoden aus FreeEntity und MeshRenderable überschreiben Interface-Methoden von Entity und Renderable,
   // keine weitere Delegation notwendig
}
Natürlich habe ich sofort nach einem Weg gesucht, dasselbe in C++ umzusetzen. Nicht ganz so schön, aber möglich:

Code: Alles auswählen

class Entity { virtual void setPosition(const vec3 &pos) = 0; ... }
class Renderable { virtual void render(Context &context) const = 0; }

template <class B>
class FreeEntity : public B { vec3 pos; ... void setPosition(const vec3 &pos) { this->pos = pos; } ... }
template <class B>
class MeshRenderable : public B { MeshData data; void render(Context &context) const { use data ... } }

class SomeRenderableEntity : public FreeEntity<Entity>, public MeshRenderable<Renderable> { };
Wirklich interessant wird es erst in Hierarchien. Prinzipiell lässt sich auch dort der Trick mit Templates anwenden, wenn auch die Konstruktordelegation eventuell in einigen Fällen schwierig wird. Ich muss los ... ;)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Weil ich im vorangegangenen Post das Thema dieses Threads mangels Zeit gewissermaßen vollständig verfehlt habe, hier nun der eigentliche Grund für meinen Post: Die C++-Variante des vorgestellten Konzepts ermöglicht nebenbei die Trennung von Schnittstelle und Implementierung ganzer Klassenhierarchien:

Code: Alles auswählen

// Interfaces verteilt über beliebig viele Headers
class InterfaceA { ... };
class InterfaceB { ... };
class InterfaceC : public Interface A { ... };

// A.cpp
template <class I> class A : public I { ... }; // Erlaubt kompakte Java/C#-Style Inline-Definitionen!

unique_ptr<InterfaceA> createA(...) { return unique_ptr<InterfaceA>( new A<InterfaceA>(...) ); }

// B.cpp
template <class I> class B : public I { ... };

unique_ptr<InterfaceB> createB(...) { return unique_ptr<InterfaceB>( new B<InterfaceB>(...) ); }

// C.cpp
#include "A.cpp"

template <class I> class C : public A<I> { ... };

unique_ptr<InterfaceC> createC(...) { return unique_ptr<InterfaceC>( new C<InterfaceC>(...) ); }
Im Gegensatz zu Templates in anderen Kontexten kann es hier praktisch zu keiner Fehlerverschleppung kommen, weil die create*()-Funktionen zusammen mit den virtuellen Interface-Methoden dafür sorgen, dass die Klassen-Templates stets vollständig instanziiert werden, sofern sie keine vollkommen unbenutzten privaten Methoden enthalten.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Nochn Muster. Statische Interfaces (keine Daten in der öffentlichen Schnittstelle) mit nur einer Implementierung:

Code: Alles auswählen

// header (*.h)
class pimpl_interface
{
   // delete assignment operator
   pimpl_interface& operator =(const pimpl_interface&); // C++11: = delete;
public:
   virtual ~pimpl_interface() { }
};

class foo : public pimpl_interface
{
   // hide constructors to block alternative implementations via inheritance
   foo() { }
   foo(const foo&) { }
public:
   // allow for one internal implementation
   class M;
   // public methods
   void publicMethod();
};

std::unique_ptr<foo> createFoo(...);

// implementation (*.cpp)
class foo::M : public foo
{
   // attributes
   int i;

   // private methods
   void privateMethod()
   {
      ++i;
   }
};

// public methods
void foo::publicMethod()
{
    M &m = static_cast<M&>(*this);
    m.privateMethod();
    ++m.i;
}

std::unique_ptr<foo> createFoo(...)
{
   return unique_ptr<foo>( new foo::M(...) );
}
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
joggel

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von joggel »

Was ist diese Klasse "M"?

[Nachtrag]
Ich habe leider nicht ganz den gesamten Thread hier mitverfolgt... wäre also möglich, das sich die Frage da beantwortet hätte.
Aber private Methoden/Member sind ja dafür da, dass sie nicht von außen benutzt werden können.
Wieso also nochmal extra "verstecken"?

Gruß
Zuletzt geändert von joggel am 21.10.2012, 19:00, insgesamt 1-mal geändert.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Die implementierende Klasse, die Daten und private Funktionalität enthält. foo::M ist die einzige Klasse, die von foo erben darf, weil alle foo-Konstruktoren privat sind und somit nur die innere Klasse M darauf Zugriff hat. Deshalb ist jede foo-Instanz mit Sicherheit auch eine foo::M-Instanz und der Cast zur Implementierung durch static_cast<M&>(*this) ist sicher.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

joggel hat geschrieben: [Nachtrag]
Ich habe leider nicht ganz den gesamten Thread hier mitverfolgt... wäre also möglich, das sich die Frage da beantwortet hätte.
Aber private Methoden/Member sind ja dafür da, dass sie nicht von außen benutzt werden können.
Wieso also nochmal extra "verstecken"?
Damit sie nicht in den Headers deklariert werden müssen und so für alle einbindenden Module unnötige Abhängigkeiten und Rebuilds verursachen.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
joggel

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von joggel »

CodingCat hat geschrieben:
joggel hat geschrieben: [Nachtrag]
Ich habe leider nicht ganz den gesamten Thread hier mitverfolgt... wäre also möglich, das sich die Frage da beantwortet hätte.
Aber private Methoden/Member sind ja dafür da, dass sie nicht von außen benutzt werden können.
Wieso also nochmal extra "verstecken"?
Damit sie nicht in den Headers deklariert werden müssen und so für alle einbindenden Module unnötige Abhängigkeiten und Rebuilds verursachen.
Ah, okay. Ja, das macht sinn ^^
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von Krishty »

joggel hat geschrieben:Wieso also nochmal extra "verstecken"?
Es gibt bestimmte Sachen, die man auch mit private nicht verstecken kann – z.B. kannst du mit sizeof(Foo) die Größe einer Klasse Foo inklusive ihrer privaten Attribute ermitteln. Dadurch entstehen implizit Abhängigkeiten im Rest des Quelltextes – z.B., dass eine umgebende Klasse, die Foo als Attribut enthält, ebenfalls neu kompiliert werden muss, wenn sich Foo ändert. Am deutlichsten ist das bei den #includes, wenn Foo bspw. eine Liste als Attribut hat: Dann muss jeder, der Foo benutzt, auch <list> #includen, obwohl das als Implementierungsdetail von Foo versteckt sein sollte. Cat versucht hier, tatsächlich 100-prozentige Entkopplung zu erreichen, wo absolut nichts einer Implementierung nach außen dringt.

(Ja zu spät aber fertiggetippt war es zu schade zum Wegschmeißen.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Ganz genau.

Im Übrigen zeige ich die Muster hier unsystematisiert und in Reinform. In meinem eigenen Quelltext bin ich längst mit einem Rudel Makros auf Qt-Niveau angekommen. Obiges Beispiel eines statischen Interfaces sähe hier in etwa so aus:

Code: Alles auswählen

// header (*.h)
class LEAN_INTERFACE foo
{
   LEAN_SHARED_STATIC_INTERFACE_BEHAVIOR(foo)

public:
   // allow for one internal implementation
   class M;
   // public methods
   void publicMethod();
};

std::unique_ptr<foo> createFoo(...);

// implementation (*.cpp)
class foo::M : public foo
{
   // attributes
   int i;

   // private methods
   void privateMethod()
   {
      LEAN_SIMPL_M
      ++m.i;
   }
};

// public methods
void foo::publicMethod()
{
    LEAN_SIMPL_M
    m.privateMethod();
    ++m.i;
}

std::unique_ptr<foo> createFoo(...)
{
   return unique_ptr<foo>( new foo::M(...) );
}
Damit bin ich zwar fernab reinen C++, dafür habe ich ein besseres Gewissen bei den Abhängigkeiten. ;)

Makros:

Code: Alles auswählen

/// Reduces overhead for purely virtual classes.
#define LEAN_INTERFACE __declspec(novtable)

/// Makes the given class behave like an interface.
#define LEAN_INTERFACE_BEHAVIOR(name) \
		protected: \
			LEAN_INLINE name& operator =(const name&) { return *this; }  \
			LEAN_INLINE ~name() { } \
		private:

/// Makes the given class behave like an interface supporting shared ownership.
#define LEAN_SHARED_INTERFACE_BEHAVIOR(name) \
		public: \
			virtual ~name() { } \
		protected: \
			LEAN_INLINE name& operator =(const name&) { return *this; }  \
		private:

/// Makes the given class behave like a static interface.
#define LEAN_STATIC_INTERFACE_BEHAVIOR(name) LEAN_INTERFACE_BEHAVIOR(name) \
		private: \
			LEAN_INLINE name() { } \
			LEAN_INLINE name(const name&) { }  \
		private:

/// Makes the given class behave like a static interface supporting shared ownership.
#define LEAN_SHARED_STATIC_INTERFACE_BEHAVIOR(name) LEAN_SHARED_INTERFACE_BEHAVIOR(name) \
		private: \
			LEAN_INLINE name() { } \
			LEAN_INLINE name(const name&) { }  \
		private:


#define LEAN_SIMPL(t, m) t &m = static_cast<t&>(*this);
#define LEAN_SIMPL_M LEAN_SIMPL(M, m)
#define LEAN_SIMPL_CM LEAN_SIMPL(const M, m)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
joggel

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von joggel »

Code: Alles auswählen

// public methods
void foo::publicMethod()
{
    M &m = static_cast<M&>(*this);
    m.privateMethod();
    ++m.i;
}
Wieso funktioniert das denn?
unter welchen C++-Standard? C++11?

Krishty hat geschrieben: Es gibt bestimmte Sachen, die man auch mit private nicht verstecken kann – z.B. kannst du mit sizeof(Foo) die Größe einer Klasse Foo inklusive ihrer privaten Attribute ermitteln. Dadurch entstehen implizit Abhängigkeiten im Rest des Quelltextes – z.B., dass eine umgebende Klasse, die Foo als Attribut enthält, ebenfalls neu kompiliert werden muss, wenn sich Foo ändert. Am deutlichsten ist das bei den #includes, wenn Foo bspw. eine Liste als Attribut hat: Dann muss jeder, der Foo benutzt, auch <list> #includen, obwohl das als Implementierungsdetail von Foo versteckt sein sollte. Cat versucht hier, tatsächlich 100-prozentige Entkopplung zu erreichen, wo absolut nichts einer Implementierung nach außen dringt.
Achso.. ja stimmt. Wird mir jetzt klar, also fast.
Das überrascht mich:
...inklusive ihrer privaten Attribute ermitteln
Aber sowas ginge ja auch, oder?

Code: Alles auswählen

// header (*.h)
class ImplClass;
class AClass
{
private:
 ImplClass* mImpl;
public:
 AClass(...)
 ~AClass()
 TypA FuncA();
 TypB FuncB();
 ...
}

Code: Alles auswählen

// implementation (*.cpp)
#include "ImplClass"

 AClass::AClass(...)
{
 mImpl = new ImplClass(...)
}
...
TypA AClass::FuncA()
{
 return mImpl->funcA();
}
...
Zuletzt geändert von joggel am 21.10.2012, 20:24, insgesamt 1-mal geändert.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

joggel hat geschrieben:

Code: Alles auswählen

// public methods
void foo::publicMethod()
{
    M &m = static_cast<M&>(*this);
    m.privateMethod();
    ++m.i;
}
Wieso funktioniert das denn? unter welchen C++-Standard? C++11?
Unter jedem mir bekannten Standard und jedem mir bekannten Compiler. Das ist nichts weiter als ein normaler Downcast. Womit hast du Probleme?
joggel hat geschrieben:Das überrascht mich:
...inklusive ihrer privaten Attribute ermitteln
Schon wenn du eine Instanz via Foo foo; anlegst muss die Größe von foo bekannt sein. Das schließt selbstverständlich sämtliche Attribute mit ein. Oder willst du auf etwas anderes hinaus?
joggel hat geschrieben:Aber sowas ginge ja auch, oder? ...
Ja, das ist das klassische PImpl-Muster, wenn auch suboptimal umgesetzt. Mehr dazu auf Herb Sutters Blog und im Eingangspost dieses Threads.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
joggel

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von joggel »

CodingCat hat geschrieben:
joggel hat geschrieben:

Code: Alles auswählen

// public methods
void foo::publicMethod()
{
    M &m = static_cast<M&>(*this);
    m.privateMethod();
    ++m.i;
}
Wieso funktioniert das denn? unter welchen C++-Standard? C++11?
Unter jedem mir bekannten Standard und jedem mir bekannten Compiler. Das ist nichts weiter als ein normaler Downcast. Womit hast du Probleme?
Das man auf private Methoden der Instanz dieser Klasse M zugreifen kann.

CodingCat hat geschrieben:
joggel hat geschrieben:Das überrascht mich:
...inklusive ihrer privaten Attribute ermitteln
Schon wenn du eine Instanz via Foo foo; anlegst muss die Größe von foo bekannt sein. Das schließt selbstverständlich sämtliche Attribute mit ein. Oder willst du auf etwas anderes hinaus?
Achso.. hatte ich falsch verstanden. Letztendlich hat man nur die Größe der privaten Attribute im Gesamten. Was dann das für welche sind ja nicht... hatte das aber irgendwie so interpretiert.
CodingCat hat geschrieben:
joggel hat geschrieben:Aber sowas ginge ja auch, oder? ...
Ja, das ist das klassische PImpl-Muster, wenn auch suboptimal umgesetzt. Mehr dazu auf Herb Sutters Blog und im Eingangspost dieses Threads.
Jo danke. werd mir das mal durchlesen.
Zuletzt geändert von joggel am 21.10.2012, 20:09, insgesamt 1-mal geändert.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

joggel hat geschrieben:
CodingCat hat geschrieben:
joggel hat geschrieben:

Code: Alles auswählen

// public methods
void foo::publicMethod()
{
    M &m = static_cast<M&>(*this);
    m.privateMethod();
    ++m.i;
}
Wieso funktioniert das denn? unter welchen C++-Standard? C++11?
Unter jedem mir bekannten Standard und jedem mir bekannten Compiler. Das ist nichts weiter als ein normaler Downcast. Womit hast du Probleme?
Dass man auf private Methoden der Instanz dieser Klasse M zugreifen kann.
Aha! Sehr guter Einwand. Weil ich mir bei Modulinterna ohnehin keine Gedanken um Sichtbarkeit mache (sprich intern alles public), habe ich dieses Detail tatsächlich übersehen. Eigentlich hatte ich auch erwartet, dass äußere Klassen auf innere genauso Vollzugriff haben wie innere auf äußere, dem ist aber tatsächlich nicht so. Also entweder modulintern Attribute nicht verstecken, oder in der inneren Klasse ein friend class Foo; hinzufügen.
joggel hat geschrieben:
CodingCat hat geschrieben: Schon wenn du eine Instanz via Foo foo; anlegst muss die Größe von foo bekannt sein. Das schließt selbstverständlich sämtliche Attribute mit ein. Oder willst du auf etwas anderes hinaus?
Achso.. hatte ich falsch verstanden. Letztendlich hat man nur die Größe der privaten Attribute im Gesamten. Was dann das für welche sind ja nicht... hatte das aber irgendwie so interpretiert.
Genau. Das macht nur leider in Bezug auf Abhängigkeiten keinen Unterschied, weil zur Berechnung der Gesamtgröße de facto der vollständige Typ eines jeden Attributs bekannt sein muss.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
joggel

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von joggel »

btw:
Ich fing ja an, mir schon Klassen-Diagramme zu malen, und hatte da ziemliche Probleme das darzustellen mit so einer internen Klasse.
Dafür gibts ja keine Klassen-Diagramm-Konvention... oder?
Benutzeravatar
RustySpoon
Establishment
Beiträge: 298
Registriert: 17.03.2009, 13:59
Wohnort: Dresden

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von RustySpoon »

joggel hat geschrieben:btw:
Ich fing ja an, mir schon Klassen-Diagramme zu malen, und hatte da ziemliche Probleme das darzustellen mit so einer internen Klasse.
Dafür gibts ja keine Klassen-Diagramm-Konvention... oder?
Doch, doch (ganz unten): http://www.sparxsystems.com.au/resource ... agram.html
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Echte private Methoden und Implementierungen

Beitrag von CodingCat »

Das Verpacken von Members in eine interne struct M { members... } m; hat den netten Nebeneffekt, dass sich Kopierkonstruktion auch vor C++11 explizit außerhalb der Klasse defaulten lässt:

Code: Alles auswählen

class Foo {
   struct M {
      int a;
      std::string b;
   } m;

public:
   Foo(const Foo &right);
};

Foo::Foo(const Foo &right)
   : m(right.m) { }
Mein Code - mein Schlachtfeld.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Antworten