канстантнасцю спасылка грамадскасць член да сябра прыватнага класа - чаму гэта працуе?

Нядаўна я знайшоў цікавую дыскусію аб тым, каб дазволіць доступ толькі для чытання да прыватных членам без заблытвання дызайну з некалькімі здабытчыкамі, і адзін з прапаноў, павінны быў зрабіць гэта наступным чынам:

#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

<Р>, калі яго канструктар завяршаецца паспяхова і вяртае нармальна. Гэта значыць, кіраванне дасягае канца цела канструктара або раней аператар вяртання.

Калі так, то няма ніякай гарантыі, што _val memeber будзе належным чынам створаны час мы выконваем _ro_val (_val) . Так як жа прыведзены вышэй код працуе? Ці з'яўляецца гэта нявызначаны паводзіны? Або прымітыўныя тыпы прадастаўлены некаторыя выключэнні жыцця аб'екта?

Можа хто-небудзь мне кропку ў нейкі спасылкі, якія б растлумачыць гэтыя рэчы?

7
Калі вы сапраўды выявіце, што вашы метады атрымання і ўстаноўкі «заблытванне дызайну», гэта можа быць нешта не так у вашым дызайне. Вы маглі б выкарыстоўваць Бог аб'ект антишаблона. См stackoverflow.com/questions/565095/& hellip;
дададзена аўтар Raedwald, крыніца
Калі вы турбуецеся пра гэта, вы павінны проста абвясціць _val першы ў сваім вызначэнні класа. Члены ініцыялізуюцца ў тым парадку, у якім яны абвешчаныя. (Але, як кажуць адказы, вам не патрэбны аб'ект ініцыялізуюцца, каб сфармаваць спасылку на яго.)
дададзена аўтар Kerrek SB, крыніца
Майце на ўвазе, што гэта прывядзе да павелічэння памеру аб'екта, і патрабуе дадатковага Ускосна доступу да сябра, у той час як убудаваны геттер будзе гэтак жа эфектыўным, як прамы доступ да сябра. Яна таксама дзівіць мяне, як значна больш заблытаным, чым геттер (так як вы павінны глядзець на вызначэнне канструктара, каб высветліць, што гэта ставіцца да), але гэта толькі маё меркаванне.
дададзена аўтар Mike Seymour, крыніца

3 адказы

Перад тым як канструктар выклікаецца адпаведны аб'ём памяці зарэзерваваны для аб'екта на FreeStore (калі вы выкарыстоўваеце новы ) або ў стэку, калі вы ствараеце аб'ект на лакальным сховішча. Гэта азначае, што памяць для _val ўжо выдзелена час вы спасылаецеся яго ў спісе инициализатора члена, толькі што гэтая памяць не ініцыялізаваны да гэтага часу.

_ro_val(_val)

Робіць апорны элемент _ro_val см памяці, выдзеленай для _val , які можа на самай справе ўтрымліваць што-небудзь ў дадзены момант часу.

Існуе яшчэ Нявызначаны паводзіны ў вашай праграме, таму што, Вы павінны відавочна ініцыялізаваць _val у 0 (ці значэння, вы выбіраеце) у целе канструктара/член Initializer list.The выхад 0 у дадзеным выпадку гэта толькі таму, што вам пашанцуе, ён можа даць вам некаторыя іншыя значэнні з моманту _val пакідаецца UNINITIALIZED. См паводзіны <моцны> тут на GCC 4.3.4, якая дэманструе UB.

Але на пытанне, Так, сапраўды паводзіны <моцны> Ну-Defined .

5
дададзена
OK, агульны пытанне, то: Дазволена Ці сфармаваць спасылку на «неинициализированный аб'ект», дзе, строга кажучы, аб'ект на самай справе не існуе? Як пустата * адр = :: аператар новы (SizeOf (T)); Т & г = * (Т *) (адрас); ?
дададзена аўтар Kerrek SB, крыніца
@ Dare2be: Так, гэта правільна.
дададзена аўтар Alok Save, крыніца
@KerrekSB: Я лічу, што нішто не перашкаджае стварэнню спасылкі на неинициализированным аб'екта , хоць спасылкі на гэта павінна быць UB ИМО.
дададзена аўтар Alok Save, крыніца
@dare: Гэта сутнасць яго, так.
дададзена аўтар Xeo, крыніца
Дык вось, калі я правільна разумею, уся раскладка памяці, дзе аб'ект будзе захоўвацца вызначаецца, перш чым мы нават да канструктара, і гэта робіць _ro_val _ спасылачныя сапраўды, ці не так?
дададзена аўтар user312650, крыніца
@Als: Re: UB ў _val; Так, вы маеце рацыю. Код быў толькі доказ канцэпцыі, так што я не клапачуся, але дзякуй - паказаўшы яго будзе карысна таму, хто яго чытае.
дададзена аўтар user312650, крыніца

адрас аб'екта не мяняецца.

гэта значыць гэта добра вызначаны.

Аднак, гэты метад паказаны толькі заўчасная аптымізацыя. Вы не эканоміць час праграмістаў. І з сучасным кампілятарам вы не захоўваеце час выканання або памер машыннага кода. Але вы робіце аб'екты ААН пераўступцы.

Cheers & hth.,

1
дададзена
"<Я> заўчасную аптымізацыю " Я б сказаў, дачасны pessimisation.
дададзена аўтар 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
дададзена