8
#include <iostream>

class A { 
  public:  
    A(){ cerr << "A Constructor" << endl; }  
    ~A(){ cerr << "A Destructor" << endl; }  
    A(const A &o){ cerr << "A Copy" << endl; } 
    A& operator=(const A &o){ cerr << "A Assignment" << endl; return *this; }
};


class B : public A { 
  public:  
    B() : A() { cerr << "B Constructor" << endl; }  
    ~B(){ cerr << "B Destructor" << endl; }
  private:
    B(const B &o) : A() { cerr << "B Copy" << endl; } 
    B& operator=(const B &o){ cerr << "B Assignment" << endl; return *this; }
};

int main() {  
  A a;  
  const A &b = B();  
  return 0; 
}

GCC 4.2では、次のメッセージが表示されます。

In function 'int main()':
Line 16: error: 'B::B(const B&)' is private
compilation terminated due to -Wfatal-errors.

Bから「プライベート」を削除すると、期待する出力が得られます。

A Constructor
A Constructor
B Constructor
B Destructor
A Destructor
A Destructor

私の質問は、プライベートと呼ばれないメソッドを作成すると、このコードがコンパイルされるかどうかが変わるのはなぜですか?これは標準で義務付けられていますか?回避策はありますか?

4

3 に答える 3

4

現在の標準(C ++ 03)の重要な言い回しは、参照が初期化される方法を説明する§8.5.3にあるようです(これらの引用符でT1は、初期化される参照T2のタイプであり、初期化式のタイプです) 。

イニシャライザ式がT2クラスタイプの右辺値であり、「 」が「 cv1 T1」と参照互換でcv2 T2ある場合、参照は次のいずれかの方法でバインドされます(選択は実装定義です)。

-参照は、右辺値(3.10を参照)で表されるオブジェクト、またはそのオブジェクト内のサブオブジェクトにバインドされます。

--タイプ" cv1 T2"[sic]の一時オブジェクトが作成され、コンストラクターが呼び出されて、右辺値オブジェクト全体が一時オブジェクトにコピーされます。参照は、一時または一時内のサブオブジェクトにバインドされます。

コピーを作成するために使用されるコンストラクターは、コピーが実際に実行されるかどうかに関係なく呼び出し可能でなければなりません。

したがって、実装が参照を一時オブジェクトに直接バインドする場合でも、コピーコンストラクターにアクセスできる必要があります。

これは、 CWG欠陥391の解決に従って、C++0xで変更されていることに注意してください。新しい言語は次のようになります(N3092§8.5.3):

それ以外の場合、T2はクラスタイプであり、

--初期化式は右辺値であり、「 」は「 、cv1 T1」と参照互換性がありますcv2 T2

--T1は参照に関連しておらずT2、初期化式は暗黙的に "型の右辺値に変換できますcv3 T3"(この変換は、該当する変換関数(13.3.1.6)を列挙し、過負荷解決(13.3)を通じて最適なものを選択することによって選択されます) 、

次に、参照は、最初のケースでは初期化子式の右辺値にバインドされ、2番目のケースでは変換の結果であるオブジェクト(または、いずれの場合も、オブジェクトの適切な基本クラスサブオブジェクト)にバインドされます。

最初のケースが適用され、参照は初期化式に「直接バインド」されます。

于 2010-07-14T19:03:48.063 に答える
3

したがって、使用しているのは「コピー初期化」です。

8.5/11イニシャライザー

初期化の形式(括弧または=を使用)は一般に重要ではありませんが、初期化されるエンティティがクラスタイプである場合は重要です。下記参照。..。

引数の受け渡し、関数の戻り、例外のスロー(15.1)、例外の処理(15.3)、および中括弧で囲まれた初期化子リスト(8.5.1)で発生する初期化は、コピー初期化と呼ばれ、次の形式と同等です。

T x = a;

新しい式(5.3.4)、static_cast式(5.2.9)、関数表記型変換(5.2.3)、およびベースとメンバーの初期化子(12.6.2)で発生する初期化は、直接初期化と呼ばれ、フォーム

T x(a);

13.3.1.3「コンストラクタによる初期化」では、選択されたコンストラクタのオーバーロードは次のとおりです。

クラス型のオブジェクトが直接初期化される場合(8.5)、または同じまたは派生クラス型の式からコピー初期化される場合(8.5)、オーバーロード解決によってコンストラクターが選択されます。直接初期化の場合、候補関数は、初期化されるオブジェクトのクラスのすべてのコンストラクターです。コピーの初期化の場合、候補関数はそのクラスのすべての変換コンストラクター(12.3.1)です。

したがって、コピーの初期化には、コピーコンストラクターが使用可能である必要があります。ただし、コンパイラはコピーを「最適化」することが許可されています。

12.2/1一時的なオブジェクト

一時オブジェクトの作成が回避された場合でも(12.8)、一時オブジェクトが作成されたかのように、すべてのセマンティック制限を尊重する必要があります。[例:コピーコンストラクターが呼び出されない場合でも、アクセシビリティ(11節)などのすべてのセマンティック制限が満たされる必要があります。]

コピー初期化を回避し、直接初期化を使用することで、必要な効果を得ることができます。

 const A &b(B());  

ノート:

GCCの新しいバージョンは明らかに異なる動作をしているので、私はこのメモを投稿すると思いました。これは違いに対処するかもしれません(両方の動作はまだ標準に準拠しています):

8.5.3 / 5参考文献によると:

初期化式が右辺値であり、T2がクラス型であり、「cv1T1」が「cv2T2」と参照互換である場合、参照は次のいずれかの方法でバインドされます(選択は実装定義です)。

  • 参照は、右辺値(3.10を参照)で表されるオブジェクトまたはそのオブジェクト内のサブオブジェクトにバインドされます。

  • タイプ「cv1T2」[原文のまま]の一時オブジェクトが作成され、コンストラクターが呼び出されて、右辺値オブジェクト全体が一時オブジェクトにコピーされます。参照は、一時または一時内のサブオブジェクトにバインドされます。

コピーを作成するために使用されるコンストラクターは、コピーが実際に実行されるかどうかに関係なく呼び出し可能でなければなりません。

私は最初に最後の文(「使用されるコンストラクター...」)を読んで両方のオプションに適用しましたが、おそらく秒のオプションにのみ適用するものとして読む必要があります-または少なくともGCCメンテナーが読んでいる方法ですそれ。

これがGCCバージョンの異なる動作の間で起こっていることであるかどうかはわかりません(コメントを歓迎します)。私たちは間違いなく私の言語弁護士スキルの限界に達しています...

于 2010-07-14T18:48:32.823 に答える
1

確かにコンパイラのバグだと思いますが、gccはそれがコピーの初期化だと思っているようです。代わりに直接初期化を使用してください。

const A& b(B());

コピー初期化でのコピーコンストラクター呼び出しは常に最適化されており(コピー省略のインスタンス)、使用可能である必要はありません。

于 2010-07-14T18:47:25.803 に答える