プライベートコンストラクターがプライベートコンストラクターではないのはいつですか?


88

タイプがあり、そのデフォルトのコンストラクタをプライベートにしたいとします。私は次のように書きます:

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
}

すごい。

しかし、その後、コンストラクターは、私が思っていたほどプライベートではないことがわかります。

class C {
    C() = default;
};

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

これは、非常に驚​​くべき、予期しない、そして明らかに望ましくない動作として私を襲います。なぜこれで問題ないのですか?


24
C c{};集約初期化ではないので、コンストラクターは呼び出されませんか?
NathanOliver

5
@NathanOliverが言ったこと。ユーザー提供のコンストラクターがないためC、集約されます。
Kerrek SB 2016

5
@KerrekSB同時に、ユーザーが明示的にctorを宣言しても、そのctorがユーザーが指定したものにならないことは、私にとってかなり驚くべきことでした。
AngewはSO

1
@Angewそれが私たち全員がここにいる理由です:)
Barry

2
@Angewもしそれが公共の俳優なら、それは=defaultより合理的に見えるでしょう。しかし、民間の=default俳優は無視してはならない重要なもののようです。さらに、class C { C(); } inline C::C()=default;まったく異なることは、いくぶん意外です。
Yakk-Adam Nevraumont 2016年

回答:


58

トリックはC ++ 14 8.4.2 / 5にあります[dcl.fct.def.default]:

...関数は、ユーザーが宣言し、最初の宣言で明示的にデフォルトまたは削除されていない場合、ユーザー提供です。...

つまり、Cのデフォルトコンストラクターは最初の宣言で明示的にデフォルト設定されていたため、実際にユーザー指定ではありません。そのCため、ユーザー提供のコンストラクターがないため、8.5.1 / 1 [dcl.init.aggr]による集計になります。

凝集体のないユーザ提供コンストラクタ(12.1)、無プライベートまたは保護された非静的データメンバ(条項11)、無基底クラス(条項10)、およびno仮想関数の配列またはクラス(条項9)(10.3であります)。


13
実際、小さな標準の欠陥:デフォルトのctorがプライベートであったという事実は、このコンテキストでは事実上無視されます。
Yakk-Adam Nevraumont 2016年

2
@ヤク私はそれを判断する資格がありません。ただし、ユーザーが提供していない俳優についての表現は非常に慎重です。
Angewは

1
@ヤク:ええ、はい、いいえ。クラスにデータメンバーがあった場合、それらを非公開にする機会があります。データメンバーがなければ、この状況が誰かに深刻な影響を与える状況はほとんどありません。
Kerrek SB 2016

2
@KerrekSBクラスをある種の「アクセストークン」を使用して、たとえば、誰がクラスのオブジェクトを作成できるかに基づいて関数を呼び出すことができるかを制御することが重要です。
Angewは、2017

5
@Yakkさらに興味深いのはC{}、コンストラクターがdeletedであっても機能することです。
Barry

55

デフォルトのコンストラクターを呼び出すのではなく、集計タイプで集計の初期化を使用しています。集約型は、最初に宣言された場所でデフォルトである限り、デフォルトのコンストラクタを持つことができます。

[dcl.init.aggr] / 1

集合体は配列またはクラス(節[クラス])で、

  • ユーザー提供のコンストラクター([class.ctor])がない(基本クラスから継承された([namespace.udecl])を含む)、
  • プライベートまたは保護された非静的データメンバー(条項[class.access])、
  • 仮想関数なし([class.virtual])、および
  • 仮想、プライベート、または保護された基本クラス([class.mi])はありません。

および[dcl.fct.def.default] / 5から

明示的にデフォルト設定された関数と暗黙的に宣言された関数はまとめてデフォルト設定関数と呼ばれ、実装はそれらの暗黙的な定義([class.ctor] [class.dtor]、[class.copy])を提供する必要があります。 。[注:関数を最初の宣言の後にデフォルトとして宣言すると、進化するコードベースへの安定したバイナリインターフェースを有効にしながら、効率的な実行と簡潔な定義を提供できます。—エンドノート]関数は、ユーザーが宣言し、最初の宣言で明示的にデフォルトまたは削除されていない場合、ユーザー提供です。ユーザー提供の明示的にデフォルト設定された関数(つまり、最初の宣言の後に明示的にデフォルト設定された関数)は、明示的にデフォルト設定されたポイントで定義されます。そのような関数が暗黙的に削除済みとして定義されている場合、プログラムは不正な形式です。

したがって、アグリゲートの要件は次のとおりです。

  • 非公開メンバーはいません
  • 仮想機能なし
  • 仮想または非公開の基本クラスはありません
  • ユーザー提供のコンストラクターは継承されず、それ以外の場合は、次のコンストラクターのみが許可されます。
    • 暗黙的に宣言されている、または
    • 同時にデフォルトとして明示的に宣言および定義されています。

C これらの要件をすべて満たします。

当然のことながら、空のデフォルトコンストラクターを提供するか、コンストラクターを宣言した後でコンストラクターをデフォルトとして定義することで、この誤ったデフォルトの構築動作を取り除くことができます。

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

2
私はこの回答がAngewの回答よりもいくらか良いと思いますが、最初は多くても2文で要約することでメリットがあると思います。
PJTraill 2016年

7

AngewjaggedSpireの回答は優れており、。そして。そして

ただし、 、状況が少し変更され、OPの例がコンパイルされなくなります。

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

2つの回答で指摘されているように、後者の2つの宣言が機能する理由Cは、が集約であり、これが集約初期化であるためです。ただし、P1008の結果(OPとあまり変わらない動機付けの例を使用)、C ++ 20での集計の定義が[dcl.init.aggr] / 1に変更されます。

集約は配列またはクラス([クラス])で、

  • ユーザーが宣言または継承したコンストラクター([class.ctor])はありません。
  • プライベートまたは保護された直接非静的データメンバー([class.access])、
  • 仮想関数なし([class.virtual])、および
  • 仮想、プライベート、または保護された基本クラス([class.mi])はありません。

鉱山を強調します。現在、要件はユーザー宣言コンストラクタではありませんが、以前は(両方のユーザーが回答で引用し、C ++ 11C ++ 14、およびC ++ 17の履歴で表示できるため)ユーザー提供コンストラクタではありませんでした。のデフォルトのコンストラクターCはユーザー宣言ですが、ユーザー提供ではないため、C ++ 20では集合体ではなくなります。


集計変更のもう1つの例を示します。

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

B基本クラスがあるため、C ++ 11またはC ++ 14では集約ではありませんでした。その結果、B{}デフォルトのコンストラクター(ユーザーが宣言したがユーザーが提供したものではない)を呼び出すだけで、Aの保護されたデフォルトのコンストラクターにアクセスできます。

C ++ 17では、P0017の結果として、基本クラスを使用できるように集計が拡張されました。Bこれは、C ++ 17のB{}集約Aです。つまり、サブオブジェクトを含むすべてのサブオブジェクトを初期化する必要がある集約初期化です。ただし、Aデフォルトのコンストラクタは保護されているため、アクセスできません。したがって、この初期化は不適切です。

C ++ 20では、Bのユーザー宣言コンストラクターのためB{}、これは再び集合体ではなくなり、デフォルトのコンストラクターの呼び出しに戻り、これも整形式の初期化です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.