简体   繁体   中英

Lifetime of std::bitset::reference object created in std::bitset::operator[]?

I have been looking at header file of bitset standard C++ library header. I found out that overloaded operator[] operator[](size_t ndx) (defined in class bitset ) returns a temproray object of class reference .

reference
    operator[](size_t __position)
{ return reference(*this,__position); }

This overloaded operator encapsulates the concept of a single bit. An instance of this class is a proxy for an actual bit. It can be useful in expressions like

bitset<10> b;
b[2] = true;

The reference class has defined overloaded = operator member function so that above example can work:

 //For b[i] = __x;
 reference&
     operator=(bool __x)
 {
   if (__x)
     *_M_wp |= _Base::_S_maskbit(_M_bpos);
   else
     *_M_wp &= ~_Base::_S_maskbit(_M_bpos);
   return *this;
}

However, i am confused over this expression:

if (b[2]) {
    //Do something
}

The b[2] first returns a temporary object of class reference and then overloaded operator ( operator bool() const ) is called on that returned temporary object to convert it to bool data type.

// For __x = b[i];
operator bool() const
{ return (*(_M_wp) & _Base::_S_maskbit(_M_bpos)) != 0; }

If temporary objects (object having automatic storage class) are created on stacks, then calling an another function ( operator bool() const ) shouldn't destroys the temporary object returned by the first function call ( reference object returned by reference operator[](size_t __position) )?

What is the lifetime of temporary objects in C and C++?

From class.temporary#4 , emphasized is mine.

When an implementation introduces a temporary object of a class that has a non-trivial constructor ([class.ctor], [class.copy]), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor ([class.dtor]). Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.

That temporary object will be destroyed at the last step of that given expression.

In fact, C++ relies on it for this very common expression can be work correctly:

int x = 0, y = 0, z = 0, t = 0;
int a = x + y + z + t;

Because x+y is a temporary, x+y+z is another temporary.


The lifetime of temporary will be shortened following rules in class.temporary#5

There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array with no corresponding initializer ([dcl.init]). The second context is when a copy constructor is called to copy an element of an array while the entire array is copied ([expr.prim.lambda], [class.copy]). In either case, if the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.

and it will be prolonged following rule in class.temporary#6 :

The third context is when a reference is bound to a temporary.116 The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

  • A temporary object bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.

  • The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

  • A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.

The first context can be seen in this example:

struct bar {
    bar() { std::cout << __func__ << '\n'; }
    bar(const bar&) { std::cout << __func__ << "__\n"; }
    ~bar() { std::cout << __func__ << '\n'; }
};

struct foo {
    foo(const bar& b = bar()) { std::cout << __func__ << '\n'; }
};

int main() {
    foo f[] = {foo(), foo()};
}

Above program should output:

bar
foo
~bar
bar
foo
~bar

The second context will be added to C++17, from then this program:

struct bar {
    bar() { std::cout << __func__ << '\n'; }
    bar(const bar&) { std::cout << __func__ << "__\n"; }
    ~bar() { std::cout << __func__ << '\n'; }
};

struct foo {
    foo() {}
    foo(const foo&, const bar& b = bar()) { std::cout << __func__ << "__\n"; }
};

struct foox {
    foo f[2];
};

int main() {
    foox fx;
    foox yx = fx;
}

must output:

bar
foo__
~bar
bar
foo__
~bar

For the third context, you can find the answer in here , and here

包含operator bool() if (b[2])的条件是单个表达式,临时值对表达式的整个生命周期有效。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM