Prev: vector question
Next: Compile time hooks, like: AClass<SomeType,Hooks<FunctorOne,FunctorTwo,...> >
From: Goran on 3 Feb 2010 20:19 On Feb 3, 3:49 pm, "Martin B." <0xCDCDC...(a)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: > -------------------------------- > #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;} > 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. -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Goran on 7 Feb 2010 20:34 On Feb 6, 2:20 am, "Martin B." <0xCDCDC...(a)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. -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
|
Pages: 1 Prev: vector question Next: Compile time hooks, like: AClass<SomeType,Hooks<FunctorOne,FunctorTwo,...> > |