Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Why you should never use a const& parameter to initialize a const& member variable!

20 views
Skip to first unread message

Martin B.

unread,
Feb 3, 2010, 9:49:49 AM2/3/10
to

myFAQ 0815 - Why you should never use a const& parameter to initialize a
const& member variable!

Today, once again, I shot myself in the foot. I thought I'd share this.

Rule: You must never use a const-reference parameter to a constructor to
initialize a const-reference member-variable.
Reason: const& parameters bind to temporaries. You do not want to track
temporaries!
Solution: Use a const* parameter

If you want a const& member variable in a class to reference something,
then it has to be initialized in the ctor. But you must not use a const&
parameter to the ctor to initialize the member, because this parameter
would bind to a temporary and then you would be tracking the temporary
instead of the original value.

Example demonstrating the issue:
--------------------------------
#include <iostream>
using namespace std;

class Bad {
int const& tracker_;

public:
explicit Bad(int const& to_track)
: tracker_(to_track)
{ }

void print() {
cout << "bad tracker_ is: " << tracker_ << endl;
}
};

class Better {
int const& tracker_;

public:
explicit Better(int const* to_track)
: tracker_(*to_track)
{ }

void print() {
cout << "better tracker_ is: " << tracker_ << endl;
}
};

int f() {
static int i = 1;
i += 5;
return i;
}


int main()
{
int t = 100;
char c = 32;

Bad a1( f() ); // compiles: bad
// Better b1( &(f()) ); - compiler error: good

Bad a2( c ); // compiles: bad
// Better b2( &c ); - compiler error: good

Bad a3( t );
Better b3( &t );

t = 166;
c = 64;
t = f();

a1.print(); // May crash or just print 6 (or whatever)
// b1.print();
a2.print(); // May crash or just print 32
// b2.print();
a3.print(); // OK
b3.print(); // OK

return 0;
}
--------------------------------

br,
Martin

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Daniel Krügler

unread,
Feb 3, 2010, 5:42:23 PM2/3/10
to

While I agree that this *can* cause a problem,
I still use references to const in examples
like yours - in C++03 you have to take care here.
I say so, because there are numerous places,
where this could cause problems - just remember
a call of the std::min or std::max functions.
If the compared objects are really large one's
I don't want to copy them, therefore I may want
to get the result by reference (to const) as well.

In C++0x there is a nice way of preventing the
problem you mention: Just add an overload of the
corresponding function which accepts an rvalue
reference of the type and define the function
as deleted:

class Bad {
int const& tracker_;
public:

explicit Bad(int const&&) = delete;


explicit Bad(int const& to_track)
: tracker_(to_track)
{ }

[...]
};

HTH & Greetings from Bremen,

Daniel Kr�gler

Martin B.

unread,
Feb 4, 2010, 8:17:40 AM2/4/10
to
Daniel Kr�gler wrote:
> On 3 Feb., 15:49, "Martin B." <0xCDCDC...@gmx.at> wrote:
>> myFAQ 0815 - Why you should never use a const& parameter to initialize a
>> const& member variable!
>>
>> Today, once again, I shot myself in the foot. I thought I'd share this.
>>
>> Rule: You must never use a const-reference parameter to a constructor to
>> initialize a const-reference member-variable.
>> Reason: const& parameters bind to temporaries. You do not want to track
>> temporaries!
>> Solution: Use a const* parameter
>>
>> If you want a const& member variable in a class to reference something,
>> then it has to be initialized in the ctor. But you must not use a const&
>> parameter to the ctor to initialize the member, because this parameter
>> would bind to a temporary and then you would be tracking the temporary
>> instead of the original value.
>>
>> Example demonstrating the issue:
>> --------------------------------
>> (...)

>> class Better {
>> int const& tracker_;
>>
>> public:
>> explicit Better(int const* to_track)
>> : tracker_(*to_track)
>> { }
>>(...)

>
> While I agree that this *can* cause a problem,
> I still use references to const in examples
> like yours - in C++03 you have to take care here.
> I say so, because there are numerous places,
> where this could cause problems - just remember
> a call of the std::min or std::max functions.
> If the compared objects are really large one's
> I don't want to copy them, therefore I may want
> to get the result by reference (to const) as well.
>

I'm not entirely sure what you mean here. My Better object still only
uses a const& member, but the initialization is done via a pointer. So
the performance should be exactly the same?

> In C++0x there is a nice way of preventing the
> problem you mention: Just add an overload of the
> corresponding function which accepts an rvalue
> reference of the type and define the function
> as deleted:
>
> class Bad {
> int const& tracker_;
> public:
> explicit Bad(int const&&) = delete;
> explicit Bad(int const& to_track)
> : tracker_(to_track)
> { }
>
> [...]
> };
>

I've never worked with C++0x. Am I correct in assuming that the deleted
function binds to temporaries (like fn return value or implicit
temporary from say a short or char) first and then the one that binds to
non-temporaries isn't considered anymore by the compiler?

br,
Martin

Goran

unread,
Feb 4, 2010, 8:19:14 AM2/4/10
to

Hmmm... I disagree. The problem here is that lifetime of stuff is bad.
"Bad" has a requirement that whatever tracker_ references must outlive
an instance of Bad. That requirement was broken through the use of a
temporary.

In C++, I personally frown upon almost any use of a pointer where
pointer can't be NULL (case here), so I don't like your idea for a
solution. If nothing else, you opened a door for this bug:

Bad b(NULL); // compiles - but bad.

In a way, you reached for a compiler help in a strange way, and for a
problem that is not a "compile-time" one ('cause, obviously, object
lifetime in C++ is the job of a programmer, not the compiler).

And indeed, even when you use a pointer, Bad/Better are still such
that pointed-to object must outlive them, so you solved less than you
intended. You solved only that particular temporary problem.

OK, I propose a vote: how many of us here were bitten by
1. use shown here, and how many have been bitten by
2. other badly sorted object lifetimes?
(me: never 1, several times 2).

So in the end, I think that one should just solve this issue through
correct coding and abstain from seeking compiler help.

BTW, similar to your situation: use of a pointer is a long-standing C-
people complaint to C++, and IMO a (rare ;-) ) valid one: they don't
like absence of "&" when passing stuff by reference, because, just by
reading code at call site, it's not visible that said parameter is a
"reference" parameter. In that vein, I quite like byref and out of C#.

Goran.

Olivier

unread,
Feb 4, 2010, 4:34:44 PM2/4/10
to
Daniel, your solution is definitely very elegant, and I looking
forward to C++0x for this. But there is still something I don't seem
to get with rvalues. Take the following code which is an extension of
your solution :

3
4 class Bad
5 {
6 public:
7 explicit Bad( int const &i ) : i_(i) { }
8 explicit Bad( int const && ) = delete;
9
10 private:
11 int const &i_;
12 };
13
14 class Bad2
15 {
16 public:
17 explicit Bad2( Bad const &t ) : t_(t) { }
18 explicit Bad2( Bad const && ) = delete;
19
20 private:
21 test const &t_;
22 };
23
24 int main( )
25 {
26 int i(23);
27
28 //Bad t0(23); // <- Kaboom! Expected.
29 Bad t1(i);
30
31 Bad2 t20(Bad(i)); // <- No Kaboom ? Why ?
32 Bad2 t21(t1);
33
34 return 0;
35 }
36

I would have (naively) expected to be able to do the same thing with
non built-in types. But this compiles fine with GCC 4.4 (line 28 blows
up if uncommented). So what i'm not getting is why does 23 bind to an
rvalue reference, where a call to Bad(i) does not ? What am I
overseeing ?

Best regards,

--
Olivier.

Martin B.

unread,
Feb 4, 2010, 4:34:45 PM2/4/10
to
Goran wrote:
> On Feb 3, 3:49 pm, "Martin B." <0xCDCDC...@gmx.at> wrote:
>> myFAQ 0815 - Why you should never use a const& parameter to initialize a
>> const& member variable!
>>
>> Today, once again, I shot myself in the foot. I thought I'd share this.
>>
>> Rule: You must never use a const-reference parameter to a constructor to
>> initialize a const-reference member-variable.
>> Reason: const& parameters bind to temporaries. You do not want to track
>> temporaries!
>> Solution: Use a const* parameter
>> (...)

>> Example demonstrating the issue:
>> --------------------------------
>> #include <iostream>
>> using namespace std;
>>
>> class Bad {
>> int const& tracker_;
>>
>> public:
>> explicit Bad(int const& to_track)
>> : tracker_(to_track)
>> { }
>>(...)

>>
>> class Better {
>> int const& tracker_;
>>
>> public:
>> explicit Better(int const* to_track)
>> : tracker_(*to_track)
>> { }
>> (...)

>
> Hmmm... I disagree. The problem here is that lifetime of stuff is bad.
> "Bad" has a requirement that whatever tracker_ references must outlive
> an instance of Bad. That requirement was broken through the use of a
> temporary.
>

Yes. But the point with temporaries is that they "are not there" in the
code, and thus it's very hard to see the error.

> In C++, I personally frown upon almost any use of a pointer where
> pointer can't be NULL (case here), so I don't like your idea for a
> solution. If nothing else, you opened a door for this bug:
>
> Bad b(NULL); // compiles - but bad.
>

This would be a rather obvious bug.
Let's look at the less obvious situation:
int* m_ptr = NULL
...
Bad o1(*m_ptr); // Bug in usage of Bad
Better o2(m_ptr); // bug in usage of Better
...
I don't see how the reference vs. pointer parameter makes the situation
better/worse.

And I'm personally completely OK with pointer parameters that must not
be NULL.

> In a way, you reached for a compiler help in a strange way, and for a
> problem that is not a "compile-time" one ('cause, obviously, object
> lifetime in C++ is the job of a programmer, not the compiler).
>
> And indeed, even when you use a pointer, Bad/Better are still such
> that pointed-to object must outlive them, so you solved less than you
> intended. You solved only that particular temporary problem.
>

Obviously if I reference anything - be it via pointer or reference - the
pointee must outlive the pointer. That wasn't the point in my example.

> ....

> So in the end, I think that one should just solve this issue through
> correct coding and abstain from seeking compiler help.
>

The situation with the Bad class in my example poses a maintenance
nightmare. Say you use it to correctly reference an int in code version
A. Then, for any reason this in is changed to an unsigned int in code
version B. Now your reference is broken but the compiler can't tell you!

br,
Martin

Vladimir Jovic

unread,
Feb 4, 2010, 4:34:45 PM2/4/10
to

And then someone comes and do this:
int main()
{
Better ops( NULL );

DeMarcus

unread,
Feb 4, 2010, 4:34:45 PM2/4/10
to
Martin B. wrote:
>
> myFAQ 0815 - Why you should never use a const& parameter to initialize a
> const& member variable!
>
> Today, once again, I shot myself in the foot. I thought I'd share this.
>
> Rule: You must never use a const-reference parameter to a constructor to
> initialize a const-reference member-variable.
> Reason: const& parameters bind to temporaries. You do not want to track
> temporaries!
> Solution: Use a const* parameter
>
> If you want a const& member variable in a class to reference something,
> then it has to be initialized in the ctor. But you must not use a const&
> parameter to the ctor to initialize the member, because this parameter
> would bind to a temporary and then you would be tracking the temporary
> instead of the original value.
>

I found this in
The C++ Programming Language by Stroustrup, Section 5.5 References

"The initializer for a plain T& must be an lvalue of type T.
The initializer for a const T& need not be an lvalue or even of type T.
In such cases,
[1] first, implicit type conversion to T is applied if necessary;
[2] then, the resulting value is placed in a temporary variable of type
T; and
[3] finally, this temporary is used as the value of the initializer.

Consider: const double& cdr = 1;
The interpretation might be:
double temp = double(1);
const double& cdr = temp;
" (end of quote)

The problem is that const has double meanings. Sometimes it means
read-only and sometimes it means constant value. In Martin's example
const means read-only, but to the compiler it means will-never-change,
hence it it freely makes a copy!

Goran

unread,
Feb 5, 2010, 5:35:32 AM2/5/10
to
On Feb 4, 10:34 pm, "Martin B." <0xCDCDC...@gmx.at> wrote:
> And I'm personally completely OK with pointer parameters that must not
> be NULL.

OK, but why? If it must not be NULL, then pointer means more typing
and more possibility to pass NULL where you should not.

But more importantly, using pointers where reference suffices means
underspecified design for little reason. I use pointer parameters only
to mark them optional.

In my experience, situations where non-optional parameter became
optional are __extremely__ rare. OTOH, I have a lot of existing code
that is using pointers almost exclusively. End result: there are
masses of places where NULL check is performed and pointer can't
possibly NULL, and it's quite unclear what should happen if that
happens (heck, there are places where there's an assert and that's all
- whoo-hoo). On the flip side, places where it can be NULL are easy to
miss, so we tend to check for NULL indiscriminately, making already
huge code base bigger.

> > In a way, you reached for a compiler help in a strange way, and for a
> > problem that is not a "compile-time" one ('cause, obviously, object
> > lifetime in C++ is the job of a programmer, not the compiler).
>
> > And indeed, even when you use a pointer, Bad/Better are still such
> > that pointed-to object must outlive them, so you solved less than you
> > intended. You solved only that particular temporary problem.
>
> Obviously if I reference anything - be it via pointer or reference - the
> pointee must outlive the pointer. That wasn't the point in my example.

Well, perhaps that's not what you wanted to talk about, but that's
exactly what happened: your pointee didn't outlive the pointer.

> > ....
> > So in the end, I think that one should just solve this issue through
> > correct coding and abstain from seeking compiler help.
>
> The situation with the Bad class in my example poses a maintenance
> nightmare.

That's a possibility, and that's why I proposed a vote (mine clearly
says that what you did is too rare in the wild to matter ;-) ). Here's
why I think it is rare: because use of references for class members is
(well, at least, should be) rare. For example, such classes you can't
copy it or use operator=. That alone excludes a myriad of uses. Also,
one should often abstain from such uses due to unclear lifetime
issues. It's not only about ref-to-temp cases such as yours: with a
class-member reference, you tie class instance to external object, and
you can't __ever__ un-tie that; that's dangerous in itself anyhow. Not
so with pointers.

In a way, I feel that your bigger error is not pass-by-ref, but use of
a reference member.

Goran.

Daniel Krügler

unread,
Feb 5, 2010, 5:34:03 AM2/5/10
to

He-he, that's a nice one ;-) Tip: Have you tried to use
t20? Just do it out of fun and you will hit a remarkable
declaration gotcha...

HTH & Greetings from Bremen,

Daniel

--

Daniel Krügler

unread,
Feb 5, 2010, 5:32:45 AM2/5/10
to
On 4 Feb., 14:17, "Martin B." <0xCDCDC...@gmx.at> wrote:
> Daniel Kr�gler wrote:

[..]

> > While I agree that this *can* cause a problem,
> > I still use references to const in examples
> > like yours - in C++03 you have to take care here.
> > I say so, because there are numerous places,
> > where this could cause problems - just remember
> > a call of the std::min or std::max functions.
> > If the compared objects are really large one's
> > I don't want to copy them, therefore I may want
> > to get the result by reference (to const) as well.
>
> I'm not entirely sure what you mean here. My Better object still only
> uses a const& member, but the initialization is done via a pointer. So
> the performance should be exactly the same?

I didn't want to imply that const& is more efficient
than your pointer approach. I only wanted to highlight
that your constructor situation belongs only to one
family of these problems. I don't assume that you
want suggest that std::min should take arguments
by pointer as well, or do you?

> > In C++0x there is a nice way of preventing the
> > problem you mention: Just add an overload of the
> > corresponding function which accepts an rvalue
> > reference of the type and define the function
> > as deleted:
>
> > class Bad {
> > int const& tracker_;
> > public:
> > explicit Bad(int const&&) = delete;
> > explicit Bad(int const& to_track)
> > : tracker_(to_track)
> > { }
>
> > [...]
> > };
>
> I've never worked with C++0x. Am I correct in assuming that the deleted
> function binds to temporaries (like fn return value or implicit
> temporary from say a short or char) first and then the one that binds to
> non-temporaries isn't considered anymore by the compiler?

That comes near. What happens is the following:
Given your example with rvalues as arguments
the compiler performs overload resolution
given the two constructors. For rvalues, the
deleted constructor overload will be selected.

*If* a deleted function is selected by
overload resolution (or by other way that would
refer to that function), the program becomes
ill-formed, which requires a diagnostic
("compile error" in our usual way of describing
the compiler's behavior).

HTH & Greetings from Bremen,

Daniel Kr�gler


Seungbeom Kim

unread,
Feb 5, 2010, 2:48:12 PM2/5/10
to
Goran wrote:
> On Feb 4, 10:34 pm, "Martin B." <0xCDCDC...@gmx.at> wrote:
>> And I'm personally completely OK with pointer parameters that must not
>> be NULL.
>
> OK, but why? If it must not be NULL, then pointer means more typing
> and more possibility to pass NULL where you should not.
>
> But more importantly, using pointers where reference suffices means
> underspecified design for little reason. I use pointer parameters only
> to mark them optional.
>
> In my experience, situations where non-optional parameter became
> optional are __extremely__ rare. OTOH, I have a lot of existing code
> that is using pointers almost exclusively. End result: there are
> masses of places where NULL check is performed and pointer can't
> possibly NULL, and it's quite unclear what should happen if that
> happens (heck, there are places where there's an assert and that's all
> - whoo-hoo). On the flip side, places where it can be NULL are easy to
> miss, so we tend to check for NULL indiscriminately, making already
> huge code base bigger.

Following a lot of advice that preferred references to pointers,
I used to employ reference data members for those with "points-to"
(or "refers to") semantics, as opposed to "has-a" semantics.
But that made the class nonassignable. So I switched to pointers.
Maybe you can keep the reference function arguments to ensure that
those pointees (or referees) are non-null and valid, but pointers
are still my preferred choice for the internal, actual data members.

Pointers are objects; references are not. This can invite some quite
weird irregularity to the behaviour of whatever is using them.

--
Seungbeom Kim

Paul Bibbings

unread,
Feb 5, 2010, 2:47:52 PM2/5/10
to
Olivier <olivie...@gmail.com> writes:

Try replacing your line 31 with:

Bad2 t20 = Bad2(Bad(i));

If you then still need help working out why your original line 31 seemed
to be accepted by the compiler when this one isn't, replace the original
with:

Bad2 t20(Bad i);

which is, in truth, a mere notational alteration that doesn't affect its
semantics at all in this instance.

Regards

Paul Bibbings

Martin B.

unread,
Feb 5, 2010, 2:48:01 PM2/5/10
to


Yes. You really have a point there. For these cases pointer members make
much more sense. My whole example was based on a refactoring case, where
using a const-ref member meant large stretches of code would not have to
be changed from dot-syntax to arrow-syntax.

br,
Martin

Francis Glassborow

unread,
Feb 5, 2010, 8:23:45 PM2/5/10
to

Reference members are very special beasts because they have to be
objects with an independent life (well that is a bit strong but I think
fundamentally correct) that is longer than the object and that needs a
user supplied copy ctor and assignment. Actually not being able to
simply copy an object is not in itself wrong.

Pointers serve two purposes, to 'hold' things whose lifetime is not the
responsibility of the object (the example I give is a person owning a
car, people own cars but they are neither responsible for their
construction nor their destruction and they may sell one car and buy
another) The other purpose is for something whose lifetime they are
responsible for. In that case a smart pointer sucj as unique_ptr is
suitable.

--

Martin B.

unread,
Feb 5, 2010, 8:20:35 PM2/5/10
to
On 05.02.2010 20:48, Seungbeom Kim wrote:
> Goran wrote:
>> On Feb 4, 10:34 pm, "Martin B." <0xCDCDC...@gmx.at> wrote:
>>> And I'm personally completely OK with pointer parameters that must not
>>> be NULL.
>>
>> OK, but why? If it must not be NULL, then pointer means more typing
>> and more possibility to pass NULL where you should not.
>>
>> But more importantly, using pointers where reference suffices means
>> underspecified design for little reason. I use pointer parameters only
>> to mark them optional.
>> (....)

>
> Following a lot of advice that preferred references to pointers,
> I used to employ reference data members for those with "points-to"
> (or "refers to") semantics, as opposed to "has-a" semantics.
> But that made the class nonassignable. So I switched to pointers.
> Maybe you can keep the reference function arguments to ensure that
> those pointees (or referees) are non-null and valid, but pointers
> are still my preferred choice for the internal, actual data members.
>
> Pointers are objects; references are not. This can invite some quite
> weird irregularity to the behaviour of whatever is using them.
>

For me the bottom line so far is that due to the semantics of references
I will try to use them only for temporary/current-callstack stuff and
never when I need to keep referencing the thing the reference points to.
I'll try to give an example:
void fA(T* p) { // or const*
// 1) work with p here
// 2) may also keep p to reference the pointee after fA has been left
}
void fB(T& r) { // or const&
// 1) work with r here
// 2) do NOT keep a reference (pointer) to r after
// fB has been left since const& references far too
// easily bind to temporaries
}

I think the pointer-parameter does a much better job of signaling to the
caller that some code may keep referencing the input argument after the
function returns than does the reference-param :-)

hope these statements made some sense,
br,
Martin

--

Keith H Duggar

unread,
Feb 6, 2010, 6:27:55 AM2/6/10
to

In my opinion that is best avoided by writing (in the first place)
and using a (probably private in this case) member function to access
it. Even for access in other member functions. In this case:

class BetterStill
{
int const * tracker_ ;
int const & tracker ( ) const { return *tracker_ ; }
public:
explicit Better(int const * to_track)
: tracker_(to_track)
{ }
void print() {
cout << "better tracker() is: " << tracker() << endl ;
}
} ;

This provides much flexibility. For example you can choose between
greedy (in the constructor) or lazy (in the accessor) verification
that tracker is non-null.

KHD

Olivier

unread,
Feb 6, 2010, 6:37:12 AM2/6/10
to
On Feb 5, 8:47 pm, Paul Bibbings <paul.bibbi...@gmail.com> wrote:

Ok, now I'm completely confused :)

Bad2 t20(Bad i);

This is totally ill-formed to me. I'm not even sure how the compiler
should/does interpret that. But :

Bad2 t20(Bad(i));

This isn't. Why is the compiler choking on this? How is it
interpreting this statement? Is this GCC specific?

Sorry, I know this is slightly off-topic, but still interesting I
thinnk.

Thanks,

--
Olivier

Paul Bibbings

unread,
Feb 6, 2010, 4:19:49 PM2/6/10
to
Olivier <olivie...@gmail.com> writes:

It is indeed interesting, and very important to be aware of. You can
get the background by searching on the internet for C++'s "most vexing
parse." A quick introduction by Danny Kalev on InformIT is:

http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=439

where he puts it in the context of what he (optimistically) calls C++09.

Regards

Paul Bibbings

--

Balog Pal

unread,
Feb 6, 2010, 4:16:49 PM2/6/10
to
"Olivier" <olivie...@gmail.com>

> Ok, now I'm completely confused :)
>
> Bad2 t20(Bad i);
>
> This is totally ill-formed to me. I'm not even sure how the compiler
> should/does interpret that.

Is it so different to
int main(int atgc, char** argv); ?

It declares a function named t20, taking one arg of type Bad, named i and
return a Bad2.

> Bad2 t20(Bad(i));
>
> This isn't.

By the C/C++ parsing rules this one is equivalent to the previous. The ()
around i are allowed and treated irrelevant.

> Why is the compiler choking on this? How is it
> interpreting this statement? Is this GCC specific?

It isn't choking, that is the whole "problem", the writer wanted an object
of type Bad2 named t20 -- but instead declared a function. It was not
apparent, because t20 was not used for anything later. USed normally, there
would be a cryptic error about unknown object t20 or a failed conversion.

The rule is ~ that any text that could be interpreted ad a declaration is
treated as declaration. Even though inside a function we rarely declare
stuff and mostly define objects. A major "gotcha" in the language,
inherited and carried from the earliest version, with the "rationale" that
you can shape object definition differently, to have the same effect, while
declaration has no flexibility.

--

Goran

unread,
Feb 8, 2010, 8:34:34 AM2/8/10
to
On Feb 6, 2:20 am, "Martin B." <0xCDCDC...@gmx.at> wrote:
> For me the bottom line so far is that due to the semantics of references
> I will try to use them only for temporary/current-callstack stuff and
> never when I need to keep referencing the thing the reference points to.
> I'll try to give an example:
> void fA(T* p) { // or const*
> // 1) work with p here
> // 2) may also keep p to reference the pointee after fA has been left}
>
> void fB(T& r) { // or const&
> // 1) work with r here
> // 2) do NOT keep a reference (pointer) to r after
> // fB has been left since const& references far too
> // easily bind to temporaries
>
> }

Yeah, that's pretty much OK.

I, for example, do use reference members, but only for
1. functor-style classes, when reference is to something that's big to
copy
2. very high-level "context" information that I am reasonably sure
can't possibly go away while code using it is running.

I feel that "2. may also keep p..." part is way too loose, because you
lose the notion of object ownership and lifetime. If I were looking at
such code, I'd always ask myself "so, can I e.g. delete p after a call
to fA or not, then?" The answer to that question is IMO too often
__elsewhere__, not at call site, and not in the function itself.

So I feel that you are looking for an easy way out WRT object
ownership and lifetime. But I frankly see no easy way out in a
language with manual memory management. IMO, that has to be enforced
through correct coding, code documentation, and coding techniques
(e.g, in C++, reference-counted pointers such as boost/
tr1::shared_ptr).

Goran.

Martin B.

unread,
Feb 8, 2010, 3:59:56 PM2/8/10
to
Goran wrote:
> On Feb 6, 2:20 am, "Martin B." <0xCDCDC...@gmx.at> wrote:
>> For me the bottom line so far is that due to the semantics of references
>> I will try to use them only for temporary/current-callstack stuff and
>> never when I need to keep referencing the thing the reference points to.
>> I'll try to give an example:
>> void fA(T* p) { // or const*
>> // 1) work with p here
>> // 2) may also keep p to reference the pointee after fA has been left}
>>
>> (....)
> (....)

>
> I feel that "2. may also keep p..." part is way too loose, because you
> lose the notion of object ownership and lifetime. If I were looking at
> such code, I'd always ask myself "so, can I e.g. delete p after a call
> to fA or not, then?" The answer to that question is IMO too often
> __elsewhere__, not at call site, and not in the function itself.
>
> So I feel that you are looking for an easy way out WRT object
> ownership and lifetime. But I frankly see no easy way out in a
> language with manual memory management. IMO, that has to be enforced
> through correct coding, code documentation, and coding techniques
> (e.g, in C++, reference-counted pointers such as boost/
> tr1::shared_ptr).
>

Obviously _if_ fA(T* p) keeps referencing the pointer, this has to be
documented! And the code has to be written accordingly. And lifetime has
to be carefully checked. There's never any way out of that.
My point was that the pointer parameter - vs. the exception param - adds
less additional complexity _if_ the function wants to keep referencing
to the pointee.

br,
Martin

Seungbeom Kim

unread,
Feb 8, 2010, 11:07:56 PM2/8/10
to
Martin B. wrote:
>
> For me the bottom line so far is that due to the semantics of references
> I will try to use them only for temporary/current-callstack stuff and
> never when I need to keep referencing the thing the reference points to.
> I'll try to give an example:
> void fA(T* p) { // or const*
> // 1) work with p here
> // 2) may also keep p to reference the pointee after fA has been left
> }
> void fB(T& r) { // or const&
> // 1) work with r here
> // 2) do NOT keep a reference (pointer) to r after
> // fB has been left since const& references far too
> // easily bind to temporaries
> }
>
> I think the pointer-parameter does a much better job of signaling to the
> caller that some code may keep referencing the input argument after the
> function returns than does the reference-param :-)

That's a convention that could certainly help in some cases, but
unfortunately it's not very widely accepted. For example, few expect
to write ostream_iterator<T>(&std::cout, "\n") or back_insert_iterator
<V>(&vector) even though the arguments are meant to outlive the function
calls or the returned objects.

--
Seungbeom Kim

Pedro Lamarão

unread,
Feb 9, 2010, 1:48:26 PM2/9/10
to
On 4 fev, 11:19, Goran <goran.pu...@gmail.com> wrote:

> In a way, you reached for a compiler help in a strange way, and for a
> problem that is not a "compile-time" one ('cause, obviously, object
> lifetime in C++ is the job of a programmer, not the compiler).

This is an interesting thing to think about, since the life time of
objects with automatic storage duration or static storage duration is
managed by the compiler.

Maybe this suggests a new language where reference syntax implies some
automatic-yet-not-garbage-collected storage duration.

Somehow, shared_ptr and unique_ptr come to mind...

--
P.

0 new messages