Const refers to a public member as a private member of the class - why does it work?

I recently found an interesting discussion on how to allow read-only access to private members without messing up the design with multiple getters, and one suggestion was to do it this way:

#include <iostream> 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; } 

Output:

 $ ./a.out 0 130 

GotW # 66 clearly states that the lifetime of the object begins

when its constructor completes successfully and returns normally. That is, the control reaches the end of the constructor body or the earlier return statement.

If this is the case, we cannot guarantee that the membber _val will be correctly created by the time _ro_val(_val) . So how does this code work? Is this behavior undefined? Or primitive types provide some exception for the life of an object?

Can someone point me to some link that would explain these things?

+7
source share
3 answers

Before the constructor will be called, the corresponding amount of memory is reserved for an object on Freestore (if you use new ) or on the stack if you create an object on local storage. This means that the memory for _val has already been allocated by the time you refer to it in the list of member initializers, only that this memory has not been properly initialized yet.

 _ro_val(_val) 

Makes the _ro_val reference element a reference to the memory allocated for _val , which may actually contain something at this point in time.

Undefined Behavior still exists in your program, because you must explicitly initialize _val to 0 (or whatever value you choose) in the constructor body / in the list of element initializers. Exiting 0 in this case is only because you are lucky, it can give you some other values, since _val remains unInitialized. See Behavior here on gcc 4.3.4, which demonstrates UB.

But as for the question, yes, indeed, the behavior is well defined .

+4
source

The address of the object does not change.

those. it is clearly defined.

However, the demonstrated technique is premature optimization. You do not save programmers time. And with a modern compiler, you do not save runtime or machine code size. But you make objects unassignable.

Cheers and hth.,

+1
source

In my opinion, it is legal (well defined) to initialize a link with an uninitialized object. This is legal, but standard (well, the latest C ++ 11 project, clause 8.5.3.3) recommends using a valid (fully constructed) object as an initializer:

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

The following sentence from the same paragraph makes link building a little easier:

[Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior.]

I understand that creating links means linking the object to the object obtained by dereferencing its pointer, and this probably explains that the minimum prerequisite for initializing a link of type T & has the address of the part of memory reserved for an object of type T (reserved, but not yet initialized )

Accessing an uninitialized object through its link can be dangerous.

I wrote a simple test application that demonstrates link initialization with an uninitialized object and the consequences of accessing this object through it:

 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; } 

Output:

 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
source

All Articles