Kiedy prywatny konstruktor nie jest prywatnym konstruktorem?


92

Powiedzmy, że mam typ i chcę ustawić jego domyślny konstruktor jako prywatny. Piszę co następuje:

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
}

Świetny.

Ale potem konstruktor okazuje się nie być tak prywatny, jak myślałem:

class C {
    C() = default;
};

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

Wydaje mi się to bardzo zaskakujące, nieoczekiwane i wyraźnie niepożądane zachowanie. Dlaczego to jest w porządku?


25
Czy nie jest C c{};inicjalizacja agregacji, więc żaden konstruktor nie jest wywoływany?
— NathanOliver

5
Co powiedział @NathanOliver. Nie masz konstruktora dostarczonego przez użytkownika, więc Cjest to agregacja.
— Kerrek SB

5
@KerrekSB W tym samym czasie było dla mnie dość zaskakujące, że użytkownik, który wyraźnie zadeklarował ctora, nie czyni go dostarczonym przez użytkownika.
— Angew nie jest już dumny z SO

1
@Angew Dlatego wszyscy tu jesteśmy :)
— Barry

2
@Angew Gdyby był to publiczny =defaultdoradca, wydawałoby się to bardziej rozsądne. Ale prywatny =defaultdoradca wydaje się ważną rzeczą, której nie należy ignorować. Co więcej, class C { C(); } inline C::C()=default;bycie całkiem innym jest nieco zaskakujące.
— Yakk - Adam Nevraumont

Odpowiedzi:


61

Sztuczka jest w C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:

... Funkcja jest dostarczana przez użytkownika, jeśli została zadeklarowana przez użytkownika i nie została jawnie ustawiona jako domyślna ani usunięta przy pierwszej deklaracji. ...

Co oznacza, że Cdomyślny konstruktor w rzeczywistości nie jest dostarczany przez użytkownika, ponieważ został jawnie ustawiony jako domyślny przy pierwszej deklaracji. W związku z tym Cnie ma konstruktorów dostarczonych przez użytkownika i dlatego jest agregatem według 8.5.1 / 1 [dcl.init.aggr]:

Agregat jest tablicą lub klasy (Rozdział 9) bez konstruktorów dostarczane przez użytkowników (12,1), ma prywatnych lub zabezpieczone nie-statycznych pól (punkt 11), brak zajęcia bazowe (punkt 10), oraz brak funkcji wirtualnej (10,3 ).


13
W efekcie mały błąd standardowy: fakt, że domyślny ctor był prywatny, jest w efekcie ignorowany w tym kontekście.
— Yakk - Adam Nevraumont

2
@Yakk, nie czuję się kwalifikowany, aby to oceniać. Sformułowanie o tym, że ctor nie jest dostarczany przez użytkownika, wygląda jednak bardzo celowo.
— Angew nie jest już dumny z SO

1
@Yakk: Cóż, tak i nie. Gdyby klasa miała jakichkolwiek członków danych, miałbyś szansę uczynić je prywatnymi. Bez członków danych jest bardzo niewiele sytuacji, w których ta sytuacja miałaby poważny wpływ na kogokolwiek.
— Kerrek SB

2
@KerrekSB Ma znaczenie, jeśli próbujesz użyć klasy jako pewnego rodzaju „tokenu dostępu”, kontrolującego np. Kto może wywołać funkcję na podstawie tego, kto może stworzyć obiekt klasy.
— Angew nie jest już dumny z SO

5
@Yakk Jeszcze bardziej interesujące jest to, że C{}działa, nawet jeśli konstruktorem jest deleted.
— Barry

56

Nie wywołujesz domyślnego konstruktora, używasz inicjalizacji agregacji na typie agregacji. Typy agregatów mogą mieć domyślny konstruktor, o ile jest on domyślny w miejscu, w którym został zadeklarowany po raz pierwszy:

Z [dcl.init.aggr] / 1 :

Agregat to tablica lub klasa (klauzula [klasa]) z

  • brak konstruktorów dostarczonych przez użytkownika ([class.ctor]) (w tym odziedziczonych ([namespace.udecl]) z klasy bazowej),
  • brak prywatnych lub chronionych niestatycznych składowych danych (klauzula [class.access]),
  • brak funkcji wirtualnych ([class.virtual]) i
  • brak wirtualnych, prywatnych lub chronionych klas bazowych ([class.mi]).

i z [dcl.fct.def.default] / 5

Funkcje z jawnie domyślnymi funkcjami i niejawnie zadeklarowane funkcje są zbiorczo nazywane funkcjami domyślnymi, a implementacja powinna zapewnić ich niejawne definicje ([class.ctor] [class.dtor], [class.copy]), co może oznaczać zdefiniowanie ich jako usuniętych . Funkcja jest dostarczana przez użytkownika, jeśli została zadeklarowana przez użytkownika i nie została jawnie ustawiona jako domyślna ani usunięta przy pierwszej deklaracji. Podana przez użytkownika funkcja z jawnie ustawioną domyślną wartością (tj. Jawnie ustawiona jako domyślna po jej pierwszej deklaracji) jest definiowana w miejscu, w którym została jawnie ustawiona jako domyślna; jeśli taka funkcja jest niejawnie zdefiniowana jako usunięta, program jest nieprawidłowo sformułowany.[Uwaga: Zadeklarowanie funkcji jako domyślnej po jej pierwszej deklaracji może zapewnić wydajne wykonanie i zwięzłą definicję, jednocześnie umożliwiając stabilny interfejs binarny dla rozwijającej się bazy kodu. - notatka końcowa]

Zatem nasze wymagania dotyczące kruszywa są następujące:

  • brak członków niepublicznych
  • brak funkcji wirtualnych
  • brak wirtualnych lub niepublicznych klas podstawowych
  • żadne konstruktory dostarczone przez użytkownika nie są dziedziczone ani inne, co zezwala tylko na konstruktory, które są:
    • niejawnie zadeklarowana lub
    • jawnie zadeklarowane i jednocześnie zdefiniowane jako niewykonane.

C spełnia wszystkie te wymagania.

Oczywiście możesz pozbyć się tego fałszywego domyślnego zachowania konstrukcji, po prostu dostarczając pustego domyślnego konstruktora lub definiując konstruktor jako domyślny po zadeklarowaniu go:

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

2
Ta odpowiedź podoba mi się nieco lepiej niż ta autorstwa Angew, ale myślę, że przydałoby się podsumowanie na początku w co najwyżej dwóch zdaniach.
— PJTraill

7

Angew's i jaggedSpire są doskonałe i odnoszą się do. I. I.

Jednak w , rzeczy się trochę zmieniają i przykład w PO nie będzie się już kompilować:

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

Jak wskazano w dwóch odpowiedziach, powodem, dla którego dwie ostatnie deklaracje działają, jest to, że Cjest to agregacja, a jest to inicjalizacja agregacji. Jednak w wyniku P1008 (przy użyciu motywującego przykładu nie różniącego się zbytnio od PO), definicja zagregowanych zmian w C ++ 20 do, z [dcl.init.aggr] / 1 :

Agregat to tablica lub klasa ([klasa]) z

  • brak konstruktorów zadeklarowanych przez użytkownika lub dziedziczonych ([class.ctor]),
  • brak prywatnych lub chronionych bezpośrednich niestatycznych składowych danych ([class.access]),
  • brak funkcji wirtualnych ([class.virtual]) i
  • brak wirtualnych, prywatnych lub chronionych klas bazowych ([class.mi]).

Podkreśl moje. Teraz wymaganiem jest brak konstruktorów zadeklarowanych przez użytkownika , podczas gdy kiedyś (jak obaj użytkownicy cytują w swoich odpowiedziach i można je przeglądać historycznie dla C ++ 11 , C ++ 14 i C ++ 17 ) nie było konstruktorów dostarczonych przez użytkownika . Domyślny konstruktor dla Cjest zadeklarowany przez użytkownika, ale nie jest dostarczany przez użytkownika, dlatego przestaje być agregacją w C ++ 20.


Oto kolejny przykład ilustrujący zagregowane zmiany:

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

Bnie był agregatem w C ++ 11 ani C ++ 14, ponieważ ma klasę bazową. W rezultacie B{}po prostu wywołuje domyślny konstruktor (zadeklarowany przez użytkownika, ale nie dostarczony przez użytkownika), który ma dostęp do Achronionego konstruktora domyślnego.

W C ++ 17, w wyniku P0017 , agregaty zostały rozszerzone o klasy bazowe. Bjest agregatem w C ++ 17, co oznacza, że B{}jest to inicjalizacja agregatu, która musi zainicjować wszystkie podobiekty - w tym Apodobiekt. Ale ponieważ Adomyślny konstruktor jest chroniony, nie mamy do niego dostępu, więc ta inicjalizacja jest źle sformułowana.

W C ++ 20, z powodu Bkonstruktora zadeklarowanego przez użytkownika, ponownie przestaje być agregatem, więc B{}powraca do wywoływania domyślnego konstruktora i jest to ponownie dobrze sformułowana inicjalizacja.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.