Pass-by-reference instead of pass-by-pointer = a bad idea?

S

Steven T. Hatton

Steven said:
(e-mail address removed) wrote: [snip]
For convenience, let's use the term 'cause' to denote the
statement or expression that results in undefined behavior
'effect' to denote the undefined behavior itself.

So let's consider an example involving references:

int func(int& r)
{
return r;
}
int main()
{
int* p = 0;
return func(*p);
}

Now we both agree that the 'cause' of the undefined behavior
is the expression *p in main. The 'effect' could be anything
at any time, but let's suppose that in this case, it is a
"seg fault" during the execution of func.

You seem to think that the fact that the effect occurred in
func is somehow an indictment of func -- specifically of the
fact that it takes a reference parameter.

No. It is just a counterexample presented to demonstrate that the
compiler will not prevent you from attempting to pass a dereferenced null
pointer to func().

Sure, a compiler will not usually prevent you from causing undefined
behavior. However, the use references did not cause or contribute to
the undefined behavior.

Had that occurred in a program that otherwise used pointers, one might argue
that the anomalous use of a reference /did/ constribute to the problem.
If your point is that reference parameters are not a silver bullet
that make undefined behavior a thing of the past, then I agree.
They can help to some degree insofar as they help one reduce the
use of pointers generally.

I guess if there is a situation inwhich references are useful as parameters,
then perhaps they should be used. I haven't encountered a lot of
situations like that. Usually my function calls change the state of the
object of which the function is a member. If there are parameters, they
are typically pretty lightweight and usually not modified by the call.
The purpose of the standard is to specify what a "well-defined program"
is so this isn't much of a caveat. Once you start invoking undefined
behavior your program is no longer a C++ program as defined by the
standard.

There is a significant difference between what is forbidden and what is left
undefined. As I understand things, code that results in undefined behavior
is not an error, and the compiler is not obligated to warn you about it,
and should probably not produce an error when it encounters such code.
IOW, you're on your own.
I don't deny there are many cases where pointers are the best choice.
However, it's hard to imagine a program that doesn't (also) have, say,
local variables.

Most of the data is stored in containers which are typically operated on by
member functions of the class that owns the container.
If you want to talk about what happens *after* you invoke undefined
behavior then we're no longer talking about C++. In C++ there is no
such thing as a null reference.

My guess is, the reality of what happens is that the result of dereferencing
a null pointer /is/ used to initialize the parameter, and that's where the
segfault comes from. The Standard does not stipulate that this is an
error.
References don't preclude polymorphism.

That is correct. I was thinking too narrowly.
Refererences don't preclude polymorphism.

What I should have said is that you are not using polymorphism the way it is
used in a scenegraph. Which is really more RTTI than polymorphism.
Also, who said anything about "programming by exception"? If there
is a pointer to be dereferenced, then whoever does the dereferencing
is responsible for "knowing" that the pointer is not null -- either
by a runtime check or by understanding the invariants at that point.

I was thinking in terms of the flow control in a scene graph where it is
quite common to use a dynamic_cast in an if() condition.
Again, references don't preclude polymorphism. I'll accept that the
design domain is a factor in how many pointers one uses.

The comment about abstract interfaces was regarding the use of indirection
to hide the implementation. I believe that's called a pimpl or something
like that. I was also thinking about Apache's C++ DOM binding
recommendation:

http://xml.apache.org/xerces-c/ApacheDOMC++BindingL3.html

Ironically Qt uses references in most places where Apache uses pointers.

http://doc.trolltech.com/4.0/xml-tools.html

I went from using Xerces-J to Xerces-C++ to QtXml. That was the first time
I had encountered references and was quite confused.
 
D

David White

Steven said:

Well, that's one opinion. I write the 'const' first as well, but only
because that's what almost everyone does, so I did too and now I'm used to
it. However, the form "int const" is more appealing because 'const' is just
a modifier and there can be many modifiers in a single declaration but only
one type. I like the consistency of the type always being first. I think
that putting 'const' in front of it is a bit of a wart on what could be a
consistent, general convention to put the type first always (even though I
don't follow my own preference).

DW
 
S

Steven T. Hatton

void Foo(int*);
void Foo(int&);

void A()
{
int x(0);
int& xx(x);

Foo(xx);
}

void B()
{
int y(0);
int* yy(&y);

Foo(yy);
}

Looking at these two functions, I don't see how it's any easier to see
that the call to Foo() in B() is likely to change y than it is to see
that the call to Foo() in A() will change x.

It's been my experience with large bodies of code that try to use
pointer arguments to signal "may change the argument" (e.g., Qt) is
that it simply doesn't work very well. The theory is that seeing the
"&" at the call site tells you it's a pointer, so you should be aware
that the pointee might change. My contrived example above that the call
site can be easily missing the "&", in which case you're no better off
than you would have been with a reference -- you have to read the
documentation.

A more realistic example of losing the "&" is when a function passes on
a pointer argument to another function:

void F(int* p)
{
...

G(p);

...
}

Bob

I agree. After this discussion started I began looking at places where
pointers are used, and, indeed, they can 'vanish' from clear sight. I
typically name things that are pointers _something_ptr, (intrusive)
reference counted pointers become _something_rptr and smart pointers become
_something_sptr.
 
J

John Carson

Steven T. Hatton said:
Yes, and I believe part of the OP's argument for using references over
pointers was that it reduces the need for detecting null pointers,
and thus results in more efficient code. I don't believe that
reasoning is viable.

As I remarked at another point in this thread:

If the client code uses pointers, then it doesn't make a lot of difference
if the function accepts pointers or references; the pointer is going to have
to be dereferenced by either the client (if the function takes references)
or the function (if the function takes pointers). The gain is when *neither*
client *nor* function uses pointers.

My practice is to use pointers rarely. On those rare occasions, I have a
heightened sense of danger and take extra care not to screw up.

It is worth pointing out that if you are a keen user of pointers, you will
typically be declaring and dereferencing them more often than just when
passing function arguments.
 
S

Steven T. Hatton

John said:
As I remarked at another point in this thread:

If the client code uses pointers, then it doesn't make a lot of difference
if the function accepts pointers or references; the pointer is going to
have to be dereferenced by either the client (if the function takes
references) or the function (if the function takes pointers). The gain is
when *neither* client *nor* function uses pointers.

My practice is to use pointers rarely. On those rare occasions, I have a
heightened sense of danger and take extra care not to screw up.

It is worth pointing out that if you are a keen user of pointers, you will
typically be declaring and dereferencing them more often than just when
passing function arguments.

Well, I would not make a habbit of dereferencing them to pass to functions I
write. I just pass the pointer.

TTTT, I've been all over the map on this issue. I like RAII. That means
"hard" member variables. I /can/ pass them by reference to anything that
doesn't try to keep an handle on them. But I don't find myself passing a
lot of data. I typically operate on it with member functions of the class
where it lives.
 
R

Rick N. Backer

Well, that's one opinion. I write the 'const' first as well, but only
because that's what almost everyone does, so I did too and now I'm used to
it. However, the form "int const" is more appealing because 'const' is just
a modifier and there can be many modifiers in a single declaration but only
one type. I like the consistency of the type always being first. I think
that putting 'const' in front of it is a bit of a wart on what could be a
consistent, general convention to put the type first always (even though I
don't follow my own preference).

DW

Isn't this more of a spiritual debate. I mean if you speak English
you ask the stable attendant for a brown horse. In French, a horse
brown, because of the syntax rules for the French language. In both
cases, the programmer/client, gets a horse and its color will be
brown. :=)

Ken Wilson

Amer. Dlx. Tele, Gary Moore LP, LP DC Classic w/P90s,
Jeff Beck Strat, Morgan OM Acoustic,
Rick 360/12, Std. Strat (MIM), Mesa 100 Nomad,
Mesa F-30

"Goodnight Austin, Texas, wherever you are."
 
D

David White

Rick said:
Isn't this more of a spiritual debate. I mean if you speak English
you ask the stable attendant for a brown horse. In French, a horse
brown, because of the syntax rules for the French language. In both
cases, the programmer/client, gets a horse and its color will be
brown. :=)

But in English do you write "horse brown" at one end of a sentence and
"brown horse" elsewhere?
1. int const * const * const p;
'p' is a const pointer to a const pointer to a const int.
(brown horse, brown horse, brown horse)

2. const int * const * const p;
'p' is a const pointer to a const pointer to an int const
(brown horse, brown horse, horse brown)

DW
 
P

Phlip

Steven said:
And hope you never will need them.

Premature complexity is the root of all evil. I can change any part of my
program at whim, so I don't need to guess the future. If I ever need those
things, I change the reference to a pointer. Among other checks, I use as
much typesafety as possible, so the compiler will tell me if I missed any
dereferences.

If I am publishing an interface - if someone else might need a pointer - I
ought to write at least three reference applications for my library. They
should expose any needs.
 
P

Phlip

Rick said:
Isn't this more of a spiritual debate?

#ifndef NDEBUG
# define FALLIBLE_INT Fallible<int> *
#else
typedef int * FALLIBLE_INT;
#endif

const FALLIBLE_INT q;

Oops. The programmer tried to create a pointer to an integer that checks its
validity. They want the checks to go away in Release mode.

In Debug mode, q points to a constant fallible int. In Release mode, q is a
constant pointer to an int. That's a silent error until something tries to
change the pointer in Release mode.

This can't break:

FALLIBLE_INT const q;

(Uncompiled code warning. If anyone finds an an issue with the example,
they'l be qualified to think of a similar one that generates the same
problem.)
 
R

Richard Herring

E. Robert Tisdale said:
But the same is true if you invoke

func(a);

You don't *need* to use the returned reference (pointer).

Of course. The fact that you don't use it is in itself a hint that
something else is going on. And if you don't use it, what's the point of
having it?
Isn't it "obvious" that, in

std::cout << "i = " << 42 << std::endl;

ostream std::cout is used *thrice*?

It's "obvious" that the above looks nothing at all like a set of nested
function calls, so I don't see your point.
Would you then agree that
a function should *not* modify two or more arguments?

I wouldn't make it an absolute rule, but if two data items are so
tightly coupled that it makes no sense to modify them separately, maybe
they should be encapsulated as some kind of structure.
What's wrong with using functions in expressions?

Wrong question. You mean "What's wrong with using
functions-which-modify-their-arguments-and-also-return-them in
expressions?" and the answer is "it obfuscates the semantics."
If you are going to call one void function after another,
you might as well write your program in assembler.

I don't see why. The compiler does a much better job.
 
S

Shezan Baig

Phlip said:
#ifndef NDEBUG
# define FALLIBLE_INT Fallible<int> *
#else
typedef int * FALLIBLE_INT;
#endif

const FALLIBLE_INT q;

Oops. The programmer tried to create a pointer to an integer that checks its
validity. They want the checks to go away in Release mode.

In Debug mode, q points to a constant fallible int. In Release mode, q is a
constant pointer to an int. That's a silent error until something tries to
change the pointer in Release mode.


By using the '#define' macro, you're basically messing with the C++
type system and asking for trouble (in other places as well). Using
this instead:


#ifndef NDEBUG
typedef Fallible<int> * FALLIBLE_INT;
#else
typedef int * FALLIBLE_INT;
#endif


Now, p and q below are of the same type:

const FALLIBLE_INT p;
FALLIBLE_INT const q;

-shez-
 
S

Steven T. Hatton

E. Robert Tisdale said:
But the same is true if you invoke

func(a);

You don't *need* to use the returned reference (pointer).


Isn't it "obvious" that, in

std::cout << "i = " << 42 << std::endl;

ostream std::cout is used *thrice*?

Would that be written like this?

using namespace std;
cout.operator<<(cout.operator<<(cout.operator<<(cout,i),42),endl);


In a mathematical context what you are showing may make sense using the
'move semantics' reasoning. To wit: "I don't care about the original
value." You could also do something like overload the call(s) so they take
either a const, or a non-const, and copy the const argument. But this is a
specialized area where RTFM/S is certainly a requirement.

You can do some nifty stuff when chaining together functors. Rather than
evaluating the operands as parameter expressions, you leave them
unevaluated, and hold on to them in the operator functor. You build up a
chain of functors, and let the optimizer have at it. Then you pull it all
out at once using a conversion operator on the outermost functor.... or
something like that. §22.4.7 TC++PL(SE). In the example Stroustrup shows,
he uses const references in both the parameter lists, and in the class
members.
 
S

Steven T. Hatton

That's because MACROS SUCK!
By using the '#define' macro, you're basically messing with the C++
type system and asking for trouble (in other places as well). Using
this instead:


#ifndef NDEBUG
typedef Fallible<int> * FALLIBLE_INT;
#else
typedef int * FALLIBLE_INT;
#endif


Now, p and q below are of the same type:

const FALLIBLE_INT p;
FALLIBLE_INT const q;

-shez-

We need a lot more of that kind of thinking around here.
 
S

Steven T. Hatton

Phlip said:
Premature complexity is the root of all evil. I can change any part of my
program at whim, so I don't need to guess the future. If I ever need those
things, I change the reference to a pointer. Among other checks, I use as
much typesafety as possible, so the compiler will tell me if I missed any
dereferences.

If I am publishing an interface - if someone else might need a pointer - I
ought to write at least three reference applications for my library. They
should expose any needs.

Actually, that has the ring of truth to it. Unfortunately, at least for the
shortterm, I'm stuck with either expending a lot of effort to make
customized smart references, or I use pointers. Usually that means

osg::ref_ptr<T> t_rptr;

struct Klass {
// t_rptr_ reminds me I plan to keep a handle on the argument.
// The argument itself is a raw pointer.
Klass(T* t_rptr_):_t_rptr(t_rptr_) {}
private:
osg::ref_ptr<T> _t_rptr;
};

Klass K(t_rptr.get()); // get and pass the raw pointer held by t_rptr
 
C

Clark S. Cox III

Would that be written like this?

using namespace std;
cout.operator<<(cout.operator<<(cout.operator<<(cout,i),42),endl);

No, like this:
cout.operator<<("i = ").operator<<(42).operator<<(endl);
 
R

Richard Herring

Clark S. Cox III said:
No, like this:
cout.operator<<("i = ").operator<<(42).operator<<(endl);
That's comprehensible.
Try this:

Foo foo;
Bar bar;
Baz baz;
operator<<(operator<<(operator<<(cout, foo), bar), baz);
 
R

Rick N. Backer

But in English do you write "horse brown" at one end of a sentence and
"brown horse" elsewhere?
1. int const * const * const p;
'p' is a const pointer to a const pointer to a const int.
(brown horse, brown horse, brown horse)

2. const int * const * const p;
'p' is a const pointer to a const pointer to an int const
(brown horse, brown horse, horse brown)

DW


Fortunately no, English syntactical rules are a little tighter about
that then the syntax rules we're dealing with here. I think this is
why you see companies come up with those annoying coding guidelines,
so we spend less time busting each others chops over the nasty piece
of code they just wrote and more time actually producing something.
:) There's nothing I like walking away from faster than a verbal
scuffle over formatting. You might as well discuss religion or
politics if that happens.

Ken Wilson

Amer. Dlx. Tele, Gary Moore LP, LP DC Classic w/P90s,
Jeff Beck Strat, Morgan OM Acoustic,
Rick 360/12, Std. Strat (MIM), Mesa 100 Nomad,
Mesa F-30

"Goodnight Austin, Texas, wherever you are."
 
S

Steven T. Hatton

Rick said:
Fortunately no, English syntactical rules are a little tighter about
that then the syntax rules we're dealing with here. I think this is
why you see companies come up with those annoying coding guidelines,
so we spend less time busting each others chops over the nasty piece
of code they just wrote and more time actually producing something.
:) There's nothing I like walking away from faster than a verbal
scuffle over formatting. You might as well discuss religion or
politics if that happens.


I have never found a need for a constant pointer, as opposed to a pointer to
constant.
 
S

Steven T. Hatton

Clark said:
No, like this:
cout.operator<<("i = ").operator<<(42).operator<<(endl);
That's what I started to write, and I suspect it will also work. Looks like
Java. :D
 

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,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top