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

I

Ian

John said:
No they can't. The C++ standard, section 8.3.2/4 says:

"a null reference cannot exist in a well-defined program, because the
only way to create such a reference would be to bind it to the "object"
obtained by dereferencing a null pointer, which causes undefined behavior."

Of course, nothing actually stops programmers from dereferencing null
pointers. The fundamental point here is that if you work with references
all (or almost all) the time, then you generally don't need to
dereference pointers, null or otherwise, and thus you avoid doing
something that is dangerous.
Exactly. I was responding to the statement "If a
parameter should never be null, then don't give it the chance." -
implying that pass by reference is safe.

Passing *p where p == NULL can be worse than passing p as it's a rare
condition that is unlikely to be tested for. References give the
illusion of safety, not the reality.

I prefer to pass by reference, but have bee bitten more than once by
instances of dereferenced null pointers being passed.
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.
Which is why I prefer it. The risk of the occasional bad object is
outweighed by the syntactic convenience of pass by reference.

Ian
 
S

Steven T. Hatton

John said:
The point is that you can't tell if the function changes x.

That is correct. But if the API is consistent in its use of pointers to
indicate potential modification of parameters, you can be confident that
there are no modifications happening to non-pointer parameters.
The cost is never using non-const references, which I consider to be a
high cost.

Never? That's taking things to extremes. I've already stated that
std::eek:stream& and the like should not be excluded. I really haven't had
that much of a chance to play with Qt4 to find out how they are handling
such situations.
It is actually a prohibitive cost, because it means you can't
use libraries that use non-const references.

No. It only means that you don't do it in *your* libraries. It's just
silly to say you would never use other people's libraries because they
don't conform you your coding conventions. Unfortunately established
practices in C++ are very heterogeneous, and excluding libraries on the
basis of failing to follow some established convention would probably
exclude 90% of all C++ code _regardless_ of the convention used as a
filter.

In the real world, we have to
work with other people's code and other people's decisions, which means
that self-documenting pass-by-value schemes can't work.

Nonsense. That's equivalent to saying coding conventions are useless
because not everyone uses the same ones.
I find it rather extraordinary that a person could call a function without
knowing whether or not it changes values within the calling scope. It
really seems to me to be such a fundamental difference in function
behaviour that a programmer should know about it as a matter of course.

How can you know about what the function does the first time you see the
code? Have you ever tried to troubleshoot code in a high priority
situation in a language you have never worked in, while talking on the
phone to the semi-technical person in Korea describing the problem they are
seeing on their end? The more useful information you can glean from subtle
artifacts in the code, the more maintainable it is.
In
any event, if you pass a pointer to any function, then you have to
independently ascertain whether values within the calling scope are
changed even with C.

I'm not sure what you mean here. How is this any different with references?
The fact of the matter is, when it comes to working with shared objects as
part of the conceptual design of a system, it is simply easier to pass
pointers. Unfortunately, until we have overloaded operator.(), we have to
deal with some less than elegant consequences.
 
S

Steven T. Hatton

Ian said:
A standard C library function throwing an exception??

Ian

I was talking about the code I posted. Not specifically strlen which I
haven't used in a decade.
 
K

Kristo

Ian said:
Passing *p where p == NULL can be worse than passing p as it's a rare
condition that is unlikely to be tested for. References give the
illusion of safety, not the reality.

If you're the library writer, that's not something you should worry
about. He who dereferences a pointer is responsible for making sure
it's not null.
I prefer to pass by reference, but have bee bitten more than once by
instances of dereferenced null pointers being passed.

Then I suggest you tell your users to stop invoking UB.

Kristo
 
S

Steven T. Hatton

Kristo said:
If you're the library writer, that's not something you should worry
about. He who dereferences a pointer is responsible for making sure
it's not null.


Then I suggest you tell your users to stop invoking UB.

Kristo

Notice that this code is basically immune to null pointer problems (assuming
nv is handleded similarly):

virtual void operator()(osg::Node *node, osg::NodeVisitor* nv){
if(_doRefresh){
if(PAT_T* pat = dynamic_cast<PAT_T*>(node)){
_refresh(pat);
_doRefresh=false;
}
}
traverse(node,nv);
}

There may be some circumstances when a null pointer is exactly what you want
to pass to your function. For example, if I don't need nv, it could be set
to null by the caller. It's actually very common to do that when working
with composites.
 
S

Stuart MacMartin

As has been show elsewhere on this thread, references can be null.

....and pointers can point to garbage, even to the point that
dereferencing crashes.
And MyObject* p passes as a parameter might actually point to
YourObject.

The point of this whole discussion seems to be whether it's way too
easy
to pass a dereferenced null pointer to a parameter that is defined as
reference.
This is an error in the caller - but is it the responsibility of the
called function
to catch this error?

Stuart
 
S

Stuart MacMartin

(Wish I knew how to properly quote text in google groups.
Our ISP's newsgroup handling is currently broken)

You say:
I suspect that could lead to problems if you apply that to code from outside
your local community. It would also seem wrong to criticize others who do
not share your conventional semantics.

I suspect this particular one is not a problem.
If I ask for a reference, I don't think there's any ambiguity that I
might be allowing *(MyType*)0;

You say:
If the function has a default null pointer parameter, I am pretty
safe passing a null pointer to it.

You mentioned this before, and I didn't comment but I agree that's a
clear signal. But it does seem to me a bit restrictive to insist that
the default parameter be 0 to be sure it allows 0. Much clearer IMO to
use references and pointers to distinguish this.

You say:
Well, I use Emacs and KDevelop

Ah yes. I forgot that there have only ever been two very good
development environments, and I've been spoiled. One was Apollo's, and
the other (no flame wars please) Microsoft's. Much as there are bugs
and scalability issues in Microsoft's development environment, I'm used
to good information about what I'm calling and what I'm expected to do
as caller.

Stuart
 
S

Stuart MacMartin

You say:
BTW, my reading of the Standard suggests there is no need to check that
node
is not null before I try to dynamic_cast it. Does anybody disagree
with
that understanding?

My reply:
Yes, dynamic_cast will return 0 if passed 0.
But if this code is time-critical, don't do this from MFC.
dynamic_cast is a performance bottleneck.
It works by doing a whole pile of string compares.
(This is why our objects have virtual IsSpecificType methods that we
can invoke.)

Stuart
 
J

John Carson

Steven T. Hatton said:
No. It only means that you don't do it in *your* libraries. It's
just silly to say you would never use other people's libraries
because they don't conform you your coding conventions.
Unfortunately established practices in C++ are very heterogeneous,
and excluding libraries on the basis of failing to follow some
established convention would probably exclude 90% of all C++ code
_regardless_ of the convention used as a filter.

It is not silly at all. The point is that you want a cheap indicator of what
a function does. Just look at the function call and you know. Well, if that
is the sort of cursory study that is involved, how do you know if the
function is one of "yours" or one from some other library? Plainly you can't
know, so your cheap indicator is as likely to mislead as inform. Sensible
programmers will ignore it.

As for your extrapolation of my statement to all coding practices, the value
of most coding conventions is independent of whether other code is following
them. Take declaring function parameters const when a function doesn't
change them. That protects the coder of the function from inadvertently
changing a value. This coding practice has value for that individual
function even if no other function in the entire program follows it. That is
not the case with your recommended use of pointers because the information
content of the practice is critically dependent on how widely used it is.
How can you know about what the function does the first time you see
the code? Have you ever tried to troubleshoot code in a high priority
situation in a language you have never worked in, while talking on the
phone to the semi-technical person in Korea describing the problem
they are seeing on their end? The more useful information you can
glean from subtle artifacts in the code, the more maintainable it is.

Now we have descended into absurdity.
I'm not sure what you mean here. How is this any different with
references?

It isn't. That is my point. Even in C, your coding convention has limited
value because most of the time you need to look at the documentation/source
code to see what a function does. In C++, it has next to no value.
The fact of the matter is, when it comes to working with
shared objects as part of the conceptual design of a system, it is
simply easier to pass pointers.

An assertion for which I have heard no good arguments.
 
?

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

Stuart said:
The point of this whole discussion seems to be whether it's way too
easy to pass a dereferenced null pointer to a parameter that is defined
as reference.
This is an error in the caller - but is it the responsibility of the
called function to catch this error?

And when passing by value? Is your resposability to ensure that the caller
never dereference a null pointer to pass the value?

I think it's not so esay, you must dereference it. Then, the responsability
is yours.

And in cases when for some reason is esay to make a mistake, you can just
write a wrapper that takes a pointer, make all checks desired and calls the
original function.
 
S

Steven T. Hatton

Stuart said:
You say:
BTW, my reading of the Standard suggests there is no need to check that
node
is not null before I try to dynamic_cast it. Does anybody disagree
with
that understanding?

My reply:
Yes, dynamic_cast will return 0 if passed 0.
But if this code is time-critical, don't do this from MFC.
dynamic_cast is a performance bottleneck.
It works by doing a whole pile of string compares.
(This is why our objects have virtual IsSpecificType methods that we
can invoke.)

Stuart

That sounds a whole lot like bad implementation more than any fundamental
problem with dynamic_cast. dynamic_cast should be doable with fairly
simple numeric based comparison. GCC compares the addresses of the type
strings. Oh my! That's a Java '=='. Don't tell anybody. :)
 
S

Steven T. Hatton

Julián Albo said:
And when passing by value? Is your resposability to ensure that the caller
never dereference a null pointer to pass the value?

I think it's not so esay, you must dereference it. Then, the
responsability is yours.

And in cases when for some reason is esay to make a mistake, you can just
write a wrapper that takes a pointer, make all checks desired and calls
the original function.
If I allocate an object on the freestore, I have to keep a pointer to it if
I am to manage it, which I will certainly want to do. That means if I want
to use a reference, I have to initialize a reference using the pointer. If
it is polymorphic, I will likely want to dynamic_cast it somewhere down the
line. If I dynamic_cast a reference, I have to try/catch it, and branch
based on exception handling. That is bad design. Furthermore, we cannot
currently reseat a reference (though I believe the new move semantic
incorporates that ability). That means I have to be more carful about what
I do with a reference than I need to be with a pointer. For instance, I
cannot reuse a reference to successively handle a sequence of objects, as I
can with a pointer. I cannot have a container of references. It seems to
me the limitations of working with references are pretty widespread. IMO,
the best advantage a reference has over a pointer is the fact that (*ptr
or worse (*ptr)() is ugly.

AFAIK, references were created to facilitate operator overloading, and they
seem best suited for that kind of a role.
 
?

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

Steven said:
If I allocate an object on the freestore, I have to keep a pointer to it
if I am to manage it, which I will certainly want to do. That means if I
want to use a reference, I have to initialize a reference using the
pointer. If it is polymorphic, I will likely want to dynamic_cast it
somewhere down the line. If I dynamic_cast a reference, I have to
try/catch it, and branch based on exception handling. That is bad design.

If you don't want to use dynamic_cast of references, just don't do it. Who
forces you do do that? You can dereference after casting and checking, for
example.
That means I have to be more carful about what I do with a reference than
I need to be with a pointer. For instance, I cannot reuse a reference to
successively handle a sequence of objects, as I can with a pointer.

Then use a pointer if you want. You are not forced to choose the party of
the pointers or the party of the references.
 
N

niklasb

Steven said:
Steven said:
Larry I Smith wrote:
Steven T. Hatton wrote: [...]
AFAIK, you *can* pass a null to func() in your example, and the
compiler
will accept it. Your code will segfault when you do so. If you pass
a
pointer, you can check for null, before accessing it. I, therefore,
suggest using a pointer instead of a reference.

There is no such thing as a null reference. However, see below.
If a function takes a ref, then NULL can not be passed.
Attempting to pass NULL causes a compile error.

For example:
[snip]
func(0); // causes a compile error

In your example, you are not trying to pass null, you are trying to pass
a
literal. That results in the attempt to create a temporary object of
type int and assign it to the non-const reference.

True, 0 or NULL is the null pointer constant, which is not the same
thing as a null pointer.

Actually it is an integer literal which when assigned to a pointer to type T
is converted to a null pointer constant to T. The distinction may seem
trivial, but the error is not due to the fact that 0 is being used. It is
due to the fact that a literal is being used to initialize a temporary of
type int.

True, but that does not contradict what I said.

§4.10
"A null pointer constant is an integral constant expression (5.19)
rvalue of integer type that evaluates to zero. A null pointer constant
can be converted to a pointer type; the result is the null pointer
value of that type..."

As I pointed out, the null pointer constant is not the same thing as a
null pointer so I was agreeing with you.
The act resulting in the undefined behavior is in main where the attempt to
dereference a null pointer is made. Where the actual /behavior/ occurs is
a different story. Basically, accessing the null pointer gives
'permission' to produce undefined behavior.

Undefined simply means you've left the world of standard C++ and
your program could do anything. As some on Usenet are fond of saying,
blue demons could fly out of your nose.

You enter the land of undefined behavior as soon as you dereference
a null pointer. How and when that manifests in some particular way
in a particular case is... well... undefined.
"But," you may say, "main doesn't dereference the pointer. The
seg fault [or whatever] occurs in func!"

First, it is not unusual for a run-time error (if one occurs
at all) to happen some time after the undefined behavior that
caused it.

The segfault *is* the undefined behavior that results from the attempt to
dereference a null pointer.

A segfault is one possible consequence of doing something that causes
undefined behavior.
The C++ Standard does not define what is meant by dereferencing a pointer.

The Standard uses dereferencing as a synonym for indirectione, e.g.,

§1.9/4
"Certain other operations are described in this International
Standard as undefined (for example, the effect of dereferencing
the null pointer). [Note: this International Standard imposes no
requirements on the behavior of programs that contain undefined
behavior.]"

§8.3.2
Note: in particular, a null reference cannot exist in a well-defined
program, because the only way to create such a reference would be to
bind it to the "object" obtained by dereferencing a null pointer,
which causes undefined behavior.
§5.3.1/1
"The unary * operator performs /indirection/: the expression to which it is
applied shall be a pointer to an object type, or a pointer to a function
type and the result is an lvalue referring to the object or function to
which the expression points. If the type of the expression is ?pointer to
T,? the type of the result is ?T.?"

An lvalue is said to refer to specific storage, so I would say that pretty
much means returning an address.

An lvalue is just an expression that refers to an object or function
(§3.10) and an object occupies storage. A local variable of type int
is an lvalue, for example. This says nothing about how a C++
implementation might choose to represent pointers and references
under the hood.
No, applying the indirection operator to a variable of type pointer to type
T is the definition of a behavior. The _result_ is undefined.

Very well, the result of dereferencing a null pointer is undefined.

[snip]
For the record, I don't believe I actually said the result was a null
reference.

You said "AFAIK, you *can* pass a null to func() in your example",
and the example in question declared func as taking a parameter of
reference type.

At a practical level, I think I understand your general point that
using references instead of pointers does not really protect you
against undefined behavior. Undefined behavior could still occur
if you form a reference by dereferencing a null pointer, return
a reference to a local object, etc.

However, my response would be that it's the responsibility of
the code that initializes the reference (e.g., by dereferencing
a pointer in your example) to make sure the object actually exists.
That's the code that needs to be scrutinized for possible undefined
behavior. A function that takes a reference parameter should be
able to assume that the reference is actually bound to an object;
if it is not then the program is already undefined and the fault
lies elsewhere.
 
E

E. Mark Ping

void func(Object* object); //object can be NULL

I think the reverse is the case: if the pointer can be NULL, the only
option is a pointer.

I prefer pass-by-ref simply because the syntax is easier ('.' instead
of '->' etc.).
Any comments on why this could be a bad idea or do you think it's
just a matter of taste?

Just because your signature is func(Object& obj), it won't prevent
others from passing in pointers:

Object* pObj = foo();
func(*pObj); //was that NULL?
 
A

Andre Kostur

(e-mail address removed) (E. Mark Ping) wrote in @agate.berkeley.edu:
Just because your signature is func(Object& obj), it won't prevent
others from passing in pointers:

Without them invoking undefined behaviour first, yes it will. Any sane
programmer will check their pointers for validity before attempting to
derference it. (Either explicitly by testing it, or implicitly by the
enforcement of preconditions.).
Object* pObj = foo();
func(*pObj); //was that NULL?

Undefined behaviour. The caller has attempted to dereference NULL. Not
func()'s problem.
 
L

Larry I Smith

Steven said:
Exactly my point. It is NOT a null pointer, it is a literal.


The error on the last example is because you are passing a pointer to a non
pointer type, and has nothing to do with the value of the type. In the
pervious examples you are passing an integer literal which is likewise
illegal.

No, it is because the stupid programmer is invoking undefined
behavior by deref'ing a null pointer; it has nothing to do with
func().
 
E

E. Robert Tisdale

Mr said:
I've been thinking about passing parameters
using references instead of pointers
in order to emphasize that the parameter must be an object.

Example:

void func(Objec& object); //object must be an object

instead of

void func(Object* object); //object can be NULL

I belive that this is a good idea
since, in the reference case, it's clear that NULL is not an option.
It's also clear that NULL is an option
when a pointer is expected (stating the obvious :) ).
The code becomes somewhat more self-documenting.

Any comments on why this could be a bad idea?
Or do you think it's just a matter of taste?

Pass by reference is *always* preferred over passing a pointer.
First, you should pass a *const* reference or pointer:

void func(const Object& object);

or

void func(const Object* object);

unless the object *must* be modified in place.
Second, if your function must modify an object,
you should return a reference or pointer to that object:

Object& func(const Object& object) {
// modify object
return object;
}

or

Object* func(const Object* object) {
// modify *object
return object;
}

instead of void so that you can use the function in an expression.
Finally, whenever possible, you should return by value:

Object func(void) {
Object object;
// modify object
return object;
}
 
E

E. Robert Tisdale

E. Robert Tisdale said:
Pass by reference is *always* preferred over passing a pointer.
First, you should pass a *const* reference or pointer:

void func(const Object& object);

or

void func(const Object* object);

unless the object *must* be modified in place.
Second, if your function must modify an object,
you should return a reference or pointer to that object:

//Object& func(const Object& object) {
Object& func(Object& object) {
// modify object
return object;
}

or

//Object* func(const Object* object) {
Object* func(Object* object) {
 

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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top