const publicメンバーをprivateクラスメンバーに参照する - なぜ動作するのですか?

最近、私は複数のゲッターでデザインを難読化せずにプライベートメンバーに読み込み専用アクセスを許可する方法について興味深い議論を見つけました。この提案の1つはこのようにすることでした:

#include 

class A {
public:
  A() : _ro_val(_val) {}
  void doSomething(int some_val) {
    _val = 10*some_val;
  }
  const int& _ro_val;
private:
  int _val;
};

int main() {
  A a_instance;
  std::cout << a_instance._ro_val << std::endl;
  a_instance.doSomething(13);
  std::cout << a_instance._ro_val << std::endl;
}

出力:

$ ./a.out 
0
130

GotW#66 clearly states that object's lifetime starts

コンストラクターが正常に完了して正常に戻ったとき。つまり、コントロールはコンストラクタ本体の終わりまたは以前のreturnステートメントに到達します。

もしそうなら、我々は _ro メンバーが _ro_val(_val)を実行するまでに正しく作成されたという保証はありません。では、上記のコードはどのように動作しますか?それは未定義の動作ですか?または、プリミティブ型は、オブジェクトの存続期間に対して何らかの例外を与えられていますか?

誰かが私にそれらの事柄を説明するいくつかの参考文献を指摘できますか?

7
ゲッターやセッターが「デザインを難読化している」ことが本当にわかった場合は、デザインに何か問題があるかもしれません。あなたはGod Objectのアンチパターンを使用しているかもしれません。 stackoverflow.com/questionsを参照してください。/565095 /&hellip;
追加された 著者 Raedwald,
これを心配している場合は、まずクラス定義で _val を宣言するだけです。メンバは宣言された順に初期化されます。 (しかし、答えが言うように、オブジェクトを参照するためにオブジェクトを初期化する必要はありません。)
追加された 著者 Kerrek SB,
インラインゲッターはメンバーに直接アクセスするのと同じくらい効率的ですが、これはオブジェクトのサイズを増やし、メンバーにアクセスするために余分な間接指定が必要であることに注意してください。また、ゲッターよりはるかに難読化されています(コンストラクターの定義を見て、それが何を指しているのか見なければならないので、それは私の意見です)。
追加された 著者 Mike Seymour,

3 答え

コンストラクタが呼び出される前に、Freestore( new を使用する場合)またはローカルストレージにオブジェクトを作成する場合はスタックに適切な量のメモリが予約されます。これは、 _val のメモリが、メンバ初期化子リストで参照するときに既に割り当てられていることを意味します。このメモリはまだ初期化されていません。

_ro_val(_val)

参照メンバ _ro_val は、 _val に割り当てられたメモリを参照します。

あなたのプログラムに未定義の振る舞いが残っているので、コンストラクタ本体/メンバ初期化子リストで明示的に _val0 (または何らかの値)に初期化する必要があります。この場合の 0 の出力は、 _val がuninitializedのままであるため、他の値を与えることができて幸運なことです。 UBを示すgcc 4.3.4の ここ の動作を参照してください。

しかし、質問については、確かにその行動はよく定義されたです。

5
追加された
OK、一般的な質問:「初期化されていないオブジェクト」への参照を作成することは許されていますか?厳密に言うとオブジェクトは実際には存在しません。 のようにvoid * addr = :: operator new(sizeof(T)); T&r = *(T *)(addr);
追加された 著者 Kerrek SB,
@ dare2be:はい、それは正しいです。
追加された 著者 Alok Save,
@KerrekSB:UBのIMOであるべきですが、初期化されていないオブジェクトへの参照を作成するのは止まらないと思います。
追加された 著者 Alok Save,
@晴れ:それはそれの要点です、はい。
追加された 著者 Xeo,
だから私が正しく理解すれば、オブジェクトが格納される場所のメモリレイアウト全体がコンストラクタに到達する前に決定され、これにより _ro_val _ 参照が有効になります。
追加された 著者 user312650,
@Als:Re:_valのUB;ええ、そうです。コードは単なる概念証明だったので、私は気にしなかったが、ありがとう - それを指摘すれば誰でも読むことができます。
追加された 著者 user312650,

オブジェクトのアドレスは変更されません。

私。それは明確に定義されています。

しかしながら、示された技術はちょうど早すぎる最適化である。あなたはプログラマーの時間を節約しません。現代のコンパイラでは、実行時間やマシンコードのサイズを節約しません。しかし、あなたはオブジェクトを割り当て不可能にします。

Cheers & hth.,

1
追加された
"時期尚早最適化"私はむしろ時期尚早の鎮圧と言うでしょう。
追加された 著者 curiousguy,

私の意見では、初期化されていないオブジェクトで参照を初期化するのは合法です(明確に定義されています)。有効な(完全に構築された)オブジェクトをイニシャライザとして使用することは合法ですが標準的です(もちろん、最新のC ++ 11ドラフト、8.5.3.3段落)。推奨

A reference shall be initialized to refer to a valid object or function.

同じ段落の次の文は、参照作成時にもう少し明かりを投げます:

[注意:特に、ヌル参照は明確に定義されたプログラムに存在することはできません。そのような参照を作成する唯一の方法は、ヌルポインタの逆参照によって得られた "オブジェクト"動作]をクリックします。

I understand that reference creation means binding reference to an object obtained by dereferencing its pointer and that probably explains that the minimal prerequisite for initialization of reference of type T& is having an address of the portion of the memory reserved for the object of type T (reserved, but not yet initialized).

参照によって初期化されていないオブジェクトにアクセスすることは危険です。

初期化されていないオブジェクトとそのオブジェクトにアクセスした結果の参照初期化を示す簡単なテストアプリケーションを作成しました。

class C
{   
public: 
    int _n;

    C() : _n(123)
    { 
        std::cout << "C::C(): _n = " << _n << " ...and blowing up now!" << std::endl;       
        throw 1;
    }
};

class B
{
public: 

   //pC1- address of the reference is the address of the object it refers
   //pC2- address of the object
    B(const C* pC1, const C* pC2)
    {
        std::cout << "B::B(): &_ro_c = " << pC1 << "\n\t&_c = " << pC2 << "\n\t&_ro_c->_n = " << pC1->_n << "\n\t&_c->_n = " << pC2->_n << std::endl; 
    }
};

class A
{
    const C& _ro_c;    
    B _b;
    C _c;

public:     

   //Initializer list: members are initialized in the order how they are 
   //declared in class
    //
   //Initializes reference to _c
    //
   //Fully constructs object _b; its c-tor accesses uninitialized object 
   //_c through its reference and its pointer (valid but dangerous!)
    //
   //construction of _c fails!
    A() : _ro_c(_c), _b(&_ro_c, &_c), _c()
    {
       //never executed 
        std::cout << "A::A()" << std::endl;
    }
};

int main()
{
    try
    {
        A a;
    }
    catch(...)
    {
        std::cout << "Failed to create object of type A" << std::endl;
    }

    return 0;
}

出力:

B::B(): &_ro_c = 001EFD70
        &_c = 001EFD70
        &_ro_c->_n = -858993460
        &_c->_n = -858993460
C::C(): _n = 123 ...and blowing up now!
Failed to create object of type A
0
追加された