Wann ist ein privater Konstrukteur kein privater Konstrukteur?

Lesezeit: 6 Minuten

Wann ist ein privater Konstrukteur kein privater Konstrukteur
Barry

Nehmen wir an, ich habe einen Typ und möchte seinen Standardkonstruktor privat machen. Ich schreibe folgendes:

class C {
    C() = default;
};

int main() {
    C c;           // error: C::C() is private within this context (g++)
                   // error: calling a private constructor of class 'C' (clang++)
                   // error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
    auto c2 = C(); // error: as above
}

Toll.

Aber dann stellt sich heraus, dass der Konstruktor nicht so privat ist, wie ich dachte:

class C {
    C() = default;
};

int main() {
    C c{};         // OK on all compilers
    auto c2 = C{}; // OK on all compilers
}    

Dies scheint mir ein sehr überraschendes, unerwartetes und ausdrücklich unerwünschtes Verhalten zu sein. Warum ist das in Ordnung?

  • Ist nicht C c{}; Aggregatinitialisierung, sodass kein Konstruktor aufgerufen wird?

    – NathanOliver

    3. Juni 2016 um 15:29 Uhr

  • Was @NathanOliver gesagt hat. Sie haben also keinen vom Benutzer bereitgestellten Konstruktor C ist ein Aggregat.

    – Kerrek SB

    3. Juni 2016 um 15:35 Uhr

  • @KerrekSB Gleichzeitig war es für mich ziemlich überraschend, dass der Benutzer, der explizit einen ctor deklariert, diesen ctor nicht zum vom Benutzer bereitgestellten macht.

    – Angew ist nicht mehr stolz auf SO

    3. Juni 2016 um 15:36 Uhr

  • @Angew Deshalb sind wir alle hier 🙂

    – Barri

    3. Juni 2016 um 15:40 Uhr

  • @Angew Wenn es öffentlich wäre =default ctor, das scheint vernünftiger zu sein. Aber das Private =default ctor scheint eine wichtige Sache zu sein, die nicht ignoriert werden sollte. Was mehr, class C { C(); } inline C::C()=default; ganz anders zu sein, ist etwas überraschend.

    – Yakk – Adam Nevraumont

    3. Juni 2016 um 15:40 Uhr

Der Trick liegt in C++14 8.4.2/5 [dcl.fct.def.default]:

… Eine Funktion ist vom Benutzer bereitgestellt wenn es vom Benutzer deklariert und bei seiner ersten Deklaration nicht ausdrücklich als Standard festgelegt oder gelöscht wird. …

Was bedeutet, dass CDer Standardkonstruktor von ist eigentlich nicht vom Benutzer bereitgestellt, da es bei seiner ersten Deklaration explizit als Standard verwendet wurde. So wie, C hat keine vom Benutzer bereitgestellten Konstruktoren und ist daher ein Aggregat gemäß 8.5.1/1 [dcl.init.aggr]:

Ein Aggregat ist ein Array oder eine Klasse (Klausel 9) ohne vom Benutzer bereitgestellte Konstruktoren (12.1), keine privaten oder geschützten nicht statischen Datenelemente (Klausel 11), keine Basisklassen (Klausel 10) und keine virtuellen Funktionen (10.3).

  • Tatsächlich ein kleiner Standardfehler: Die Tatsache, dass der Standardctor privat war, wird in diesem Zusammenhang praktisch ignoriert.

    – Yakk – Adam Nevraumont

    3. Juni 2016 um 15:36 Uhr


  • @ Yakk Ich fühle mich nicht qualifiziert, das zu beurteilen. Die Formulierung, dass der Ctor nicht vom Benutzer bereitgestellt wird, sieht jedoch sehr bewusst aus.

    – Angew ist nicht mehr stolz auf SO

    3. Juni 2016 um 15:37 Uhr

  • @Yakk: Nun, ja und nein. Wenn die Klasse Datenmitglieder hätte, hätten Sie die Möglichkeit, diese privat zu machen. Ohne Datenmitglieder gibt es nur sehr wenige Situationen, in denen diese Situation jemanden ernsthaft betreffen würde.

    – Kerrek SB

    3. Juni 2016 um 15:40 Uhr

  • @KerrekSB Es ist wichtig, wenn Sie versuchen, die Klasse als eine Art “Zugriffstoken” zu verwenden, das z. B. steuert, wer eine Funktion aufrufen kann, basierend darauf, wer ein Objekt der Klasse erstellen kann.

    – Angew ist nicht mehr stolz auf SO

    3. Juni 2016 um 17:28 Uhr


  • @ Yakk Noch interessanter ist das C{} funktioniert auch, wenn der Konstruktor ist deleteD.

    – Barri

    3. Juni 2016 um 23:17 Uhr


Wann ist ein privater Konstrukteur kein privater Konstrukteur
gezackterSpire

Sie rufen nicht den Standardkonstruktor auf, sondern verwenden die Aggregatinitialisierung für einen Aggregattyp. Aggregattypen dürfen einen standardmäßigen Konstruktor haben, solange er dort standardmäßig ist, wo er zuerst deklariert wird:

Von [dcl.init.aggr]/1:

Ein Aggregat ist ein Array oder eine Klasse (Klausel [class]) mit

  • keine vom Benutzer bereitgestellten Konstruktoren ([class.ctor]) (einschließlich der geerbten ([namespace.udecl]) aus einer Basisklasse),
  • keine privaten oder geschützten nichtstatischen Datenelemente (Klausel [class.access]),
  • keine virtuellen Funktionen ([class.virtual]), und
  • keine virtuellen, privaten oder geschützten Basisklassen ([class.mi]).

und von [dcl.fct.def.default]/5

Explizit voreingestellte Funktionen und implizit deklarierte Funktionen werden zusammen als voreingestellte Funktionen bezeichnet, und die Implementierung muss implizite Definitionen für sie bereitstellen ([class.ctor] [class.dtor], [class.copy]), was bedeuten könnte, dass sie als gelöscht definiert werden. Eine Funktion wird vom Benutzer bereitgestellt, wenn sie vom Benutzer deklariert und bei ihrer ersten Deklaration nicht explizit als Standard festgelegt oder gelöscht wird. Eine vom Benutzer bereitgestellte Funktion mit explizitem Standardwert (dh mit explizitem Standardwert nach ihrer ersten Deklaration) wird an dem Punkt definiert, an dem sie explizit mit dem Standardwert versehen wird; Wenn eine solche Funktion implizit als gelöscht definiert ist, ist das Programm falsch formatiert. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]

Somit sind unsere Anforderungen an ein Aggregat:

  • keine nicht-öffentlichen Mitglieder
  • keine virtuellen Funktionen
  • keine virtuellen oder nicht-öffentlichen Basisklassen
  • keine vom Benutzer bereitgestellten Konstruktoren geerbt oder anderweitig, was nur Konstruktoren zulässt, die:
    • implizit deklariert, bzw
    • explizit deklariert und gleichzeitig als ausgefallen definiert.

C erfüllt all diese Anforderungen.

Natürlich können Sie dieses falsche Standardkonstruktionsverhalten beseitigen, indem Sie einfach einen leeren Standardkonstruktor bereitstellen oder den Konstruktor als Standard definieren, nachdem Sie ihn deklariert haben:

class C {
    C(){}
};
// --or--
class C {
    C();
};
inline C::C() = default;

  • Diese Antwort gefällt mir etwas besser als die von Angew, würde aber meiner Meinung nach von einer Zusammenfassung am Anfang in höchstens zwei Sätzen profitieren.

    – PJTrail

    8. Juni 2016 um 10:13 Uhr

Wann ist ein privater Konstrukteur kein privater Konstrukteur
Barry

Die Antworten von Angew und jaggedSpire sind ausgezeichnet und gelten für c++11. Und c++14. Und c++17.

In c++20 ändern sich die Dinge jedoch ein wenig und das Beispiel im OP wird nicht mehr kompiliert:

class C {
    C() = default;
};

C p;          // always error
auto q = C(); // always error
C r{};        // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20

Wie aus den beiden Antworten hervorgeht, funktionieren die beiden letztgenannten Deklarationen, weil C ist ein Aggregat und dies ist die Aggregat-Initialisierung. Allerdings als Folge P1008 (unter Verwendung eines motivierenden Beispiels, das dem OP nicht allzu unähnlich ist), die Definition von aggregierten Änderungen in C++20 zu, von [dcl.init.aggr]/1:

Ein Aggregat ist ein Array oder eine Klasse ([class]) mit

  • Nein vom Benutzer deklariert oder geerbte Konstruktoren ([class.ctor]),
  • keine privaten oder geschützten direkten nicht statischen Datenelemente ([class.access]),
  • keine virtuellen Funktionen ([class.virtual]), und
  • keine virtuellen, privaten oder geschützten Basisklassen ([class.mi]).

Betonung von mir. Jetzt ist die Anforderung nein vom Benutzer deklariert Konstruktoren, während es früher war (wie beide Benutzer in ihren Antworten zitieren und historisch für angesehen werden können C++11, C++14und C++17) Nein vom Benutzer bereitgestellt Konstrukteure. Der Standardkonstruktor für C wird vom Benutzer deklariert, aber nicht vom Benutzer bereitgestellt, und ist daher in C++ 20 kein Aggregat mehr.


Hier ist ein weiteres anschauliches Beispiel für aggregierte Änderungen:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

B war kein Aggregat in C++11 oder C++14, da es eine Basisklasse hat. Als Ergebnis, B{} ruft nur den Standardkonstruktor (vom Benutzer deklariert, aber nicht vom Benutzer bereitgestellt) auf, auf den Zugriff besteht ADer geschützte Standardkonstruktor von .

In C++17 als Ergebnis von P0017wurden Aggregate erweitert, um Basisklassen zu ermöglichen. B ist ein Aggregat in C++17, was bedeutet, dass B{} ist eine Aggregat-Initialisierung, die alle Unterobjekte initialisieren muss – einschließlich der A Unterobjekt. Aber weil ADer Standardkonstruktor von ist geschützt, wir haben keinen Zugriff darauf, daher ist diese Initialisierung falsch formatiert.

In C++20 wegen B‘s vom Benutzer deklarierter Konstruktor ist, hört es wieder auf, ein Aggregat zu sein, also B{} kehrt zum Aufrufen des Standardkonstruktors zurück, und dies ist wieder eine wohlgeformte Initialisierung.

917340cookie-checkWann ist ein privater Konstrukteur kein privater Konstrukteur?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy