📜 ⬆️ ⬇️

C ++: When the lifetime of an object is determined by the lifetime of a link to it

At that time, there are articles about the essence and pitfalls of r-value links (an example with links to useful sources habrahabr.ru/post/157961 ) I suspect that quite a few people do not know the features of ordinary l-value links. The essence of this article is to set an example when the lifetime of an object is determined by the lifetime of the l-value reference to it, and how it can be used. If interested, then welcome. By the way, knowing as many features as possible about l-value links, it will be easier to understand r-value.

We can assume that all transmit objects by a constant link, when it is necessary and quite accurately know the lifetime of the object.
For example:
struct S{}; void f(const S& value){} f(S()); 

In this case, we can assume that the object S () will begin to collapse after calling the function f() . Why pretty accurate? - Because, in the case of q(A(), B()); The order of creation and, accordingly, the destruction of objects A and B is not defined. Everyone knows that it is impossible to write
 int& r = 1; //   

And now the fun part.
But you can do this:
 const int& r = 1; 
In this case, according to the standard and Stroustrup (7.7.1)
Those. in the following example
 struct Obj { Obj(int i) : m_i(i) { cout << "ctr: " << m_i << endl; } ~Obj() { cout << "dtr: " << m_i << endl; } Obj operator+(const Obj& value) { return Obj(m_i + value.m_i); } int m_i; }; ... Obj o1(1); const Obj& ro2 = Obj(2) + Obj(3); Obj o6(6); 

Output (msvs 2012):
 ctr: 1 ctr: 3 ctr: 2 ctr: 5 dtr: 2 dtr: 3 ctr: 6 dtr: 6 dtr: 5 dtr: 1 

But that's not all. Everybody also knows why a virtual destructor is needed, but let's consider the following example when the base class has a non-virtual destructor. Continue to use our Obj and add
 struct D : Obj { D(int i) : Obj(i) { cout << "D::ctr: " << m_i << endl; } ~D() { cout << "D::dtr: " << m_i << endl; } }; Obj o1(1); const Obj& ro2 = D(5); Obj o6(6); 
Conclusion:
 ctr: 1 ctr: 5 D::ctr: 5 ctr: 6 dtr: 6 D::dtr: 5 dtr: 5 dtr: 1 
Those. in this case, despite the fact that the type of the constant reference is const Obj& , nevertheless, our object D “lives” while “lives” a link to it.

Then the question arises: “And what is the practical use?”. One of the answers is already used in the ScopeGuard approach ( http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758?pgno=2 ). I personally would not use such an approach and would wrap the necessary resource handle into the class with the appropriate destructor and designer.

Well, as impressions, and now remember about implicit type conversion in case the constructor is not declared as explicit, type derivation in template functions and the article at the beginning of the post.
')
I hope that someone this article has opened another feature of C ++.

Application and comments.

To protect those who do not climb into the standard and only beginners With ++ programmers, I remind you that in the case of
 const Obj& f() {return Obj();} 
the temporary object will collapse before exiting the function, and the returned reference will be a bat. The lifetime of an object is determined only by a local reference. It will be harder to say more concise and clearer than the standard; if it is interesting, then start with paragraph 12.2. Here is a quotation from the standard (which quite often meets in all sorts of bagzillah and forums):
The second context is when a reference is bound to a temporary. There are no limits on the number of people who are living there. A constructor's reference to the constructor (12.6.2). A callback (5.2.2) is given in the function call.


In the course of writing this article I came across an article by H. Sutter http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
Plus an interesting example http://www.rsdn.ru/forum/cpp/4257549.flat
 #include <iostream> struct foo { ~foo() { std::cout << "~foo()\n"; } }; struct foo_holder { const foo &f; }; int main() { foo_holder holder = { foo() }; std::cout << "done!\n"; return 0; } 
I would suggest that the conclusion should be
 ~foo() done! 

Because in this case the temporary object is used in the expression that is the initializer, and then, as in the case of ordinary functions, the lifetime of the temporary object does not extend longer than the expression.
But in practice, the result is slightly different.
Output (msvs 2012):
 ~foo() done! ~foo() 
And (g ++ (Ubuntu / Linaro 4.7.3-1ubuntu1) 4.7.3):
 done! ~foo() 


Thank you for your attention, have a nice day.

Source: https://habr.com/ru/post/186790/


All Articles