Жеке мүшенің жеке мүшесіне сілтеме жасау - неге ол жұмыс істейді?

Жақында мен жеке мүшелерге оқуға ғана рұқсат алу мүмкіндігін талқылауға мүмкіндік берілді, алайда бірнеше ұсыныс жасаған дизайнды алдаусыз болды.

#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 нысанына (егер new пайдалансаңыз) немесе жергілікті қоймада нысан жасасаңыз, стекеде сақталады. Бұл _val үшін жады бұрынғыдай мүше инициализатор тізімінде сілтеме жасаған уақытқа бөлінгенін білдіреді. Алайда, бұл жад әлі де дұрыс бапталмаған.

_ro_val(_val)

_ro_val анықтамалық мүшесін _val үшін бөлінген жадқа сілтеме жасайды, ол шын мәнінде осы уақытта ештеңені қамтымауы мүмкін.

Сіздің бағдарламаңызда әлі де анықталмаған мінез-құлық бар, өйткені конструктордағы/мүше Initializer тізімінде _val сөзін 0 (немесе кейбір мәнді таңдап алсаңыз) айқындауға керек. Бұл жағдайда _val _val шығу unInitialized қалдырылғандықтан, сізге басқа да мәндерді беруі мүмкін. мұнда мінез-құлқын қараңыз.

Бірақ Сұрақ болсақ, Иә, шын мәнінде мінез-құлық анықталған .

5
қосылды
Жақсы, жалпы мәселе: «объект жоқ объектіге» сілтеме жасауға рұқсат етілген бе? Like void * addr = :: жаңа оператор (sizeof (T)); T & r = * (T *) (addr); ?
қосылды автор Kerrek SB, көзі
@ dare2be: Ия, бұл дұрыс.
қосылды автор Alok Save, көзі
@KerrekSB: Менің ойымша, ештеңе uninitialized object сілтемесін жасауды тоқтатады, бірақ оны UB IMO болуы керек.
қосылды автор Alok Save, көзі
@dare: Бұл оның мағынасы, иә.
қосылды автор Xeo, көзі
Сондықтан егер мен дұрыс түсінсем, нысанның сақталуы үшін барлық жад макеті конструкторға жетуден бұрын анықталады және бұл _ro_val_ сілтемесін жарамды етеді, дұрыс?
қосылды автор user312650, көзі
@Als: Re: UB in _val; Ия, сіз дұрыссыз. Кодекс тұжырымдаманың дәлелі болды, сондықтан мен өзіме қамқорлық жасамады, бірақ рахмет айтсам, оны оқитын адамға пайдалы болады.
қосылды автор 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
қосылды