Dangling Reference. Idiom

B

Belebele

Suppose that I have a method that returns references to "elements" in
an iterator, and a method to advance the iterator:

class Element { /* ... */ };

class Iterator {
public:
Element& operator*() const;

Iterator& operator++();

/* ... */
};


Now, I also want the clients of the iterator to know that the
reference to element returned by the operator* is only valid
temporarily. That is, clients of the iterator are not supposed to hold
the element reference beyond the next call to operator++, for it will
be dangling. See below:

Iterator iter(/* ... */);
Element &e = *iter;
/* Do something with e */
++iter; // At this point the reference e becomes dangling.


I would like to convey that semantic constraint to the client. A
comment would probably be sufficient. However, I would like to explore
the possibility of expressing the constraint in code.

It occurred to me to use and ancillary class to do so:

struct WillDangleRef { // Ancillary class
operator Element&() const;

private:
WillDangleRef(WillDangleRef const& ); // Hidden. Not Implemented

Element &element_;

WillDangleRef(Element& e): element_(e) {}
};


class Iterator {
public:
WillDangleRef operator*() const; // The operator* now returns
an object that
// can only be
used for conversions into Element&

Iterator& operator++();

/* ... */
};

The WillDangleRef temporary will be destructed right after the
statement that contains the call to the operator*, which gives a clue
as to the transient nature of the reference that it can be converted
into. That leaves clients with the only choice of using the reference
as a parameter to a function, which I do not like very much because I
feel is too restrictive:

void doSomethingWithE(Element& e);

Iterator iter(/* ... */);
doSomethingWithE(*iter);
++iter; // At this point the temporary returned by *iter is gone.


Could you please comment? Any other ideas?

Thanks:

Belebele
 
V

Victor Bazarov

Belebele said:
Suppose that I have a method that returns references to "elements" in
an iterator, and a method to advance the iterator:

class Element { /* ... */ };

class Iterator {
public:
Element& operator*() const;

Iterator& operator++();

/* ... */
};


Now, I also want the clients of the iterator to know that the
reference to element returned by the operator* is only valid
temporarily. That is, clients of the iterator are not supposed to hold
the element reference beyond the next call to operator++, for it will
be dangling. See below:

Iterator iter(/* ... */);
Element &e = *iter;
/* Do something with e */
++iter; // At this point the reference e becomes dangling.


I would like to convey that semantic constraint to the client. A
comment would probably be sufficient. However, I would like to explore
the possibility of expressing the constraint in code.

It occurred to me to use and ancillary class to do so:

struct WillDangleRef { // Ancillary class
operator Element&() const;

private:
WillDangleRef(WillDangleRef const& ); // Hidden. Not Implemented

Element &element_;

WillDangleRef(Element& e): element_(e) {}
};


class Iterator {
public:
WillDangleRef operator*() const; // The operator* now returns
an object that
// can only be
used for conversions into Element&

Iterator& operator++();

/* ... */
};

The WillDangleRef temporary will be destructed right after the
statement that contains the call to the operator*, which gives a clue
as to the transient nature of the reference that it can be converted
into. That leaves clients with the only choice of using the reference
as a parameter to a function, which I do not like very much because I
feel is too restrictive:

void doSomethingWithE(Element& e);

Iterator iter(/* ... */);
doSomethingWithE(*iter);
++iter; // At this point the temporary returned by *iter is gone.


Could you please comment? Any other ideas?

A BAD IDEA(tm). Iterator should not be the owner of the object to
which gives access. Iterator should only provide a way to refer to
some particular element _elsewhere_ (like a container). If the object
to which the iterator refers does not survive after the iterator is
incremented, you shouldn't return a reference, you should probably
return a copy of the object (i.e. return by value).

As to your "WillDangleRef", what prevents me from doing

WillDangleRef const& ref = *it; ++it;

? Will 'ref' survive? It should, I bound a reference to it. Now,
right after the next statement whatever 'ref' allows me to access
is still invalid, isn't it?

V
 
B

Belebele

A BAD IDEA(tm). Iterator should not be the owner of the object to
which gives access.

I would prefer if we steer the discussion away from the fact that I
named my class Iterator. I get the impression that you are using your
preconceived ideas about iterators to shape the discussion. My
intention is to discuss the method that returns a temporary reference
which will become dangling according to some rule.

However, I would like to discuss that concept of the iterator, but as
a separate topic, once we find some common ground with the topic of
the reference.
As to your "WillDangleRef", what prevents me from doing

WillDangleRef const& ref = *it; ++it;

? Will 'ref' survive? It should, I bound a reference to it. Now,
right after the next statement whatever 'ref' allows me to access
is still invalid, isn't it?

My understanding is that the temporary WillDangleRef object will be
destroyed as soon as the statement where it is created completes
execution. In this case, that statement is:

WillDangleRef const& ref = *it;

Thus, the reference ref will become dangling immediately. Using ref
will cause undefined behavior. My point in creating the auxiliary
class was to convey to clients (who are aware of the life-time rules
of temporaries) that the element reference provided by calling the
WillDangleRef object's 'operator Element&' is invalid right after the
statement that created the WillDangleRef object.
 
P

Pete Becker

Victor said:
A BAD IDEA(tm). Iterator should not be the owner of the object to
which gives access.

Just one qualification: that's sometimes how input iterators works,
which is why there are so many restrictions on what you can do with an
input iterator. But that sort of thing should definitely be avoided.
And, in the case of input iterators, operator* returns a value, not a
reference, so you can hang on to its result regardless of what you
subsequently do with the iterator.

--

-- Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com)
Author of "The Standard C++ Library Extensions: a Tutorial and
Reference." (www.petebecker.com/tr1book)
 
V

Victor Bazarov

Belebele said:
I would prefer if we steer the discussion away from the fact that I
named my class Iterator. I get the impression that you are using your
preconceived ideas about iterators to shape the discussion.

I am not shaping the discussion. And, yes, I have plenty of "my
preconceived ideas" about many things in this world. That's what is
known as "experience". If you want to break out of the mold while
reusing terms bound to that mold, perhaps you should have established
the terminology first...
My
intention is to discuss the method that returns a temporary reference
which will become dangling according to some rule.

Fine. Then you should have used a neutral name for your type, like
'Foo'. You invited the criticism about the "Iterator" by using that
term.
However, I would like to discuss that concept of the iterator, but as
a separate topic, once we find some common ground with the topic of
the reference.

Please, by all means.
My understanding is that the temporary WillDangleRef object will be
destroyed as soon as the statement where it is created completes
execution.

No, it will not. It will be destroyed at the same time 'ref' (the
reference bound to it) is destroyed. If you would like to experiment,
here is some code:

#include <iostream>
struct dtor_outputs {
~dtor_outputs() { std::cout << "~dtor_outputs\n"; }
};
dtor_outputs foo() {
return dtor_outputs();
}
int main() {
dtor_outputs const& ref = foo();
std::cout << "Belebele thinks this will be after d-tor\n";
std::cout << "Bazarov thinks this will be before d-tor\n";
}

, see what you get.
In this case, that statement is:

WillDangleRef const& ref = *it;

Thus, the reference ref will become dangling immediately. Using ref
will cause undefined behavior. My point in creating the auxiliary
class was to convey to clients (who are aware of the life-time rules
of temporaries) that the element reference provided by calling the
WillDangleRef object's 'operator Element&' is invalid right after the
statement that created the WillDangleRef object.

I *got* your point. My point is that even with that class you cannot
protect the reference against becoming invalid. That's why you're
better off not confusing your users but simply documenting the existing
behaviour.

V
 
B

Belebele

My
Fine. Then you should have used a neutral name for your type, like
'Foo'. You invited the criticism about the "Iterator" by using that
term.

I apologize for choosing a rather misleading class name. Names are so
important ...
No, it will not. It will be destroyed at the same time 'ref' (the
reference bound to it) is destroyed. If you would like to experiment,
here is some code:

#include <iostream>
struct dtor_outputs {
~dtor_outputs() { std::cout << "~dtor_outputs\n"; }
};
dtor_outputs foo() {
return dtor_outputs();
}
int main() {
dtor_outputs const& ref = foo();
std::cout << "Belebele thinks this will be after d-tor\n";
std::cout << "Bazarov thinks this will be before d-tor\n";
}

, see what you get.

Right on the money. Now, is that behavior specified by the standard,
or is it compiler-specific?
Please, by all means.

Let me put my ideas together. I will start another topic related to
this and I would be delighted to discuss with you a couple of design
ideas that involve iterators for an abstraction layer here at work.

Once again, thanks.
 
?

=?ISO-8859-15?Q?Juli=E1n?= Albo

Belebele said:
I would prefer if we steer the discussion away from the fact that I
named my class Iterator. I get the impression that you are using your
preconceived ideas about iterators to shape the discussion.

Most people here has the preconceived idea that people choose names that
does not increment the noise level in the discussion.
 
N

Noah Roberts

Belebele said:
That is, clients of the iterator are not supposed to hold
the element reference beyond the next call to operator++, for it will
be dangling. See below:

Iterator iter(/* ... */);
Element &e = *iter;

Simple, don't supply this interface. Don't let them dereference the
iterator with *. Then they can only use -> and the problem is solved.

Of course, your "iterator" won't work as an iterator but names aren't
important so...
 
V

Victor Bazarov

Belebele wrote:
[..]
I said:
Right on the money. Now, is that behavior specified by the standard,
or is it compiler-specific?

It is specified by the Standard.

Binding a reference to const in order to prolong the life of some
temporary obtained from an expression is not a common idiom. It
is not unheard of, though. Of course, even in the program above,
you're likely to see

dtor_outputs obj = foo();

rather than what I wrote. The compiler is allowed to eliminated
extraneous temporary objects when constructing 'obj'.

And since constructing an object is not necessarily expensive, you
might want to actually make your class return an object by value
instead of a reference, thus eliminating the lifetime issue fully.
I think I even saw Pete Becker suggest the same thing.

V
 
P

Pete Becker

Victor said:
And since constructing an object is not necessarily expensive, you
might want to actually make your class return an object by value
instead of a reference, thus eliminating the lifetime issue fully.
I think I even saw Pete Becker suggest the same thing.

Yes, but that was back when I thought iterator meant iterator. <g> One
of the underlying assumptions of the STL and, hence, the design of
iterators and algorithms, is that the objects that these things deal
with are cheap to create and to copy. Dealing with heavy objects often
means allocating them on the heap and managing them through smart
pointers, which also probably makes the original code cleaner.

--

-- Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com)
Author of "The Standard C++ Library Extensions: a Tutorial and
Reference." (www.petebecker.com/tr1book)
 
G

Grizlyk

Belebele said:
Now, I also want the clients of the iterator to know that the
reference to element returned by the operator* is only valid
temporarily.
Could you please comment? Any other ideas?

I think client must assuming that if iterator has changed " ++it " all data
accessable via the iterator or cached by the iterator is has changed also.
The simplest kind of iterator is counter

char buf[10];
int it=0;
&buf[it] != &but[it+1];

If client walking over container, client must use data befor iterator
changed, or use several iterators (they are designed for it) or make copyes
of data

const Element tmp1(*it); ++it;
const Element tmp2(*it); ++it;
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,573
Members
45,046
Latest member
Gavizuho

Latest Threads

Top