Never ever use a raw pointer when a smart pointer can do the same job

A

Alf P. Steinbach

* Hicham Mouline:
It is interesting to see so many different opinions about this.
Perhaps, the discussion can be fine tuned if I put down the use case
(trivial one) here:

We have a 3rd party application that accepts to load shared dynamic
libraries, this is on windows.
The application requires a number of entry points into the DLL. These are
"exported" functions, 2 of these
are

// with C linkage convention (ie , they use C and not C++)
int initialize(some_type* f);
void finalize();

1 translation unit where we implement this requirement looks like



extern "C" {

static const our_adapter* adapter=0; // global static raw pointer

int initialize(const some_type* f)
{
if (f==0)
{
return failure; /// 3rd application will ignore this dll
}
adapter = new our_adapter(*f); /// ctor never fails. If new fails, the
3rd party application crashes (we decided it's ok)
}

//// we axiomatize that initialize() is called once before finalize() and
that the 3rd party application calls these just once
void finalize()
{
delete adapter;
}

// other required function
int other_function()
{
adapter->do_something();
}

}


I would have had as a global variable the our_adapter itself, but its ctor
requires the "f", so I used a pointer instead.
Because of the exisiting symmetry between initalize and finalize, new'ing
and deleting seem to me appropriate.

My colleague insists I should use auto_ptr<our_adapter> instead of a raw
pointer here, and then said as a general rule,
never use a raw pointer when the smart pointer will work, hence the post.

could I have opinions about this particular case?

It mixes in irrelevant concerns: platform-specific shared libraries,
singleton-with-arguments. Both of which are quite complex.

Anyway, in the above about the only thing a smart pointer could help with would
be exception safety in the 3rd party application, ensuring that it would call
finalize.

And for that you'd not change the 'adapter' pointer into a smart pointer (it
doesn't matter for the 'adapter' pointer, it's just a case of personal
preference),. You would instead provide a higher level interface (inline in a
header) that gives the 3rd party application a smart pointer. So the example is
an example where a smart pointer would be useful, but not as a simple
replacement of the current raw pointer.


Cheers & hth.,

- Alf
 
J

Juha Nieminen

Hicham said:
... my colleague says.

I disagree on the "Never".

Opinions about this "rule of thumb" welcome.

I would like to see him implement a doubly-linked list with smart
pointers.
 
R

Ron

Never use a pointer when an object with proper initialization,
allococation, copy, and
destruction semantics exists.
 
N

Noah Roberts

BGB said:
when doing custom memory management;
when working with data which is "not an object" (loosely structured or
unstructured regions of memory, ...);
...

basically, not all tasks involve "objects", sometimes the task may involve
something with far less "identity" or "structure"...

AKA: raw pointers, for raw memory...

std::vector<char>

Very few cases when that isn't completely appropriate for that need.
 
N

Noah Roberts

James said:
Ah, but those are smart pointers:). Not the type Noah was
thinking of, I think, but they're certainly more like smart
pointers than they are iterators. (In normal usage, "random
access iterator" is an oxymoron.)

There's all kinds of smart pointers.
 
N

Noah Roberts

Nick said:
Victor said:
Hicham Mouline wrote:
["Never ever use a raw pointer when a smart pointer can do the same
job"] my colleague says.
I disagree on the "Never".
Opinions about this "rule of thumb" welcome.
What's the definition of "the same job"? They are for different jobs.
[...] I use raw pointer when I need to use a raw pointer and I use a
smart pointer when I need to use a smart pointer.
What purpose does a raw pointer serve?

to point to things...
Almost any case when you need a heap allocated data blob can be solved
by the use of raii objects such as std::vector [etc.]
If any of those object do what you need you should use
them because NOT using raii leaves you with all the problems raii answers.

but RAII is not the only reson to use pointers.

RAII is never a reason to use pointers.
 
V

Victor Bazarov

Ron said:
Never use a pointer when an object with proper initialization,
allococation, copy, and
destruction semantics exists.

What about 'move' semantics? Do they belong?

V
 
N

Noah Roberts

James said:
That's not the case in any of the applications I've worked on.
At the application level, lifetime is generally very
deterministic; objects are created and deleted as a result of
specific input from external sources. Using a scoped_ptr,
shared_ptr or an auto_ptr on any of these will result in a
double delete. (It's sometimes useful to use an auto_ptr during
the actual construction, until the transaction has completed, or
the object has been registered wherever it has to be
registered.)

All of the above allow you to delete the object explicitly. That is
almost certainly a required feature of any smart pointer that is
involved with resource ownership.
RAII isn't really very appropriate for managing the lifetime of
dynamically allocated object, since RAII depends on lifetime
corresponding to a scope, and if the lifetime of an object
corresponds to a scope, then you don't allocate it dynamically.

That's certainly not true. There are numerous reasons to dynamically
allocate something that is still tied to some kind of scope. For instance:

void do_something_with_chars(??)
{
std::vector<char> buffer(some calculated size);
... do stuff ...
}

This is better than using new to create an array because "... do stuff
...." could be exception generating code. It's just plain easier to
write exception safe code with RAII and, in fact, sometimes it's the
only way.
In certain cases, shared_ptr can serve as a poor man's garbage
collector, but it's very error prone (compared to a real garbage
collector).

Not in my experience. You certainly don't use it naively but I've seen
no reason to believe a gc would do the job better. For one thing a gc
isn't often deterministic to the application code and thus RAII isn't
even possible (and RAII has many uses beyond working with memory
resources, it can be used to guard any allocatable resource).
Unless it's an optional argument, where you might want to pass
a null pointer.

Yes I forgot about that one. I mentioned a couple exceptions but that
wasn't in the list. There is of course a way around even this though
and in many cases is the better answer: null object.
Or have to reseat it.

Covered in () by a.
I'm curious as to the type of software you work on.

Desktop applications.

You
obviously have some experience, but in all of the application
software I've seen (low level libraries are a different case),
dynamically allocated objects which have lifetimes corresponding
to those of a local variable are extremely rare---almost all
have lifetimes which begin and end in response to an external
stimulus. And almost all pointers are used for navigation, and
only navigation.

Even when pointers are used "only for navigation" it is of benefit to
use something like a shared_ptr. The problem with pointers that are
"only for navigation" is that the objects that have them are dependent
upon the lifetime of other objects and thus are intimately coupled with
them...but they often don't even know about them. I do sometimes ignore
this problem but I often regret that choice. A shared_ptr is not
usually an expense that overweights the expense of debugging a dangling
pointer.

There are of course times when you actually would prefer a dangling
pointer to an object outliving what is conceived as its useful lifetime.
Either way you look at it you probably have a bug on your hands when
an object that has a "navigation only" pointer exists longer than the
object owning the thing it points to. However, often times the path to
recreate this bug is difficult to recreate and if just having the thing
do weird stuff the user never sees is preferable to a crash.

And designs change. It's easier to say, "Now I want this object to live
longer," if the pointers it's using are shared.

Further, you actually have ways to have a "navigation only" pointer that
can be checked before you access it. In the case of a raw pointer you
have to trust that it is either there, or has been set to NULL. If both
sides are using the shared_ptr system, one the owner (having a
shared_ptr) and the other the referrer (having a weak_ptr) then when the
owner goes away we know that everyone having a "navigation only" pointer
to whatever parts of it it's shared know the resource is gone.

Of course, these are all things that any good developer takes care of
100% of the time, right? Nobody ever leaves a dangling pointer or
neglects setting something to NULL... It is true that really rigerous
practices and good developers that are very observant all the time might
be able to get it right all the time. In my experience that's just too
much to ask even of myself. I **** up sometimes but if I'm using a
device that renders my fuckups nil or impossible it definitely makes my
job easier on both ends, coding and debugging.

Then you have the cases when your "navigation only" pointer points at a
stack allocated object. Believe it or not you can still use smart
pointers here. Hold a boost::shared_ptr to your local stack object (and
you can just stick these in a boost::any array if you want), return
weak_ptr objects from your accessor method. Granted, easily broken so
there's probably a better way (and I don't necessarily recommend that
method) with some smart pointer object of your own but the idea is that
everyone who has a pointer now has some method of checking that the
navigation is still available before using it.

On top of all that though there are many practices that create
dynamically generated objects that are totally reliant on scope. My
array example is one, pimpl (or handle/body) is another and is one we
use a lot.

There are other methods one can take to respond to these problems but I
find expecting all pointers to be some form of "smart" pointer object is
the most straightforward, easiest to check in code reviews, and
objectively enforceable as a standard. Any time you see a raw pointer
you ask, "Why did you do that?" Then you all have the option of
designing a smart pointer that addresses the particular problem that no
other did...or live with the raw pointer.
 
N

Noah Roberts

Ron said:
Never use a pointer when an object with proper initialization,
allococation, copy, and
destruction semantics exists.

This is probably a better wording. When I say I prefer smart pointers
over raw pointers I mean to refer to what you're talking about. There
are many constructs that solve problems raw pointers are traditionally
meant to solve but do better and they're not all a shared_ptr or some
sort of scoped_ptr. For instance, using std::vector instead of dynamic
arrays is almost always preferred but most probably wouldn't consider
that a "smart pointer", though really that's essentially what it is.
 
N

Noah Roberts

Alf said:
* Hicham Mouline:

It mixes in irrelevant concerns: platform-specific shared libraries,
singleton-with-arguments. Both of which are quite complex.

Anyway, in the above about the only thing a smart pointer could help
with would be exception safety in the 3rd party application, ensuring
that it would call finalize.

And for that you'd not change the 'adapter' pointer into a smart pointer
(it doesn't matter for the 'adapter' pointer, it's just a case of
personal preference),. You would instead provide a higher level
interface (inline in a header) that gives the 3rd party application a
smart pointer. So the example is an example where a smart pointer would
be useful, but not as a simple replacement of the current raw pointer.

I totally agree with everything you just said. Simply slapping a smart
pointer on a raw pointer without thinking is not a good approach. You
should think about what the raw pointer is for and then analyze what
construct would best smarten it up.

On the other hand, it's not going to harm anything to do it. If they're
trying to get you to adhere to a standard in development then even
enforcing it in cases like this, though they may seem pedantic and
stupid, might be the best move on their part.

In fact, one case an auto_ptr would help is if initialize is called
multiple times. Your code never actually checks to see if it already
exists, ergo you could get a memory leak if a client program called the
function repeatedly. An auto_ptr would delete the previous reference
and then accept the new one...no leak.
 
N

Noah Roberts

Victor said:
What about 'move' semantics? Do they belong?

I can't think of a reason to include move semantics in that list. Not
having actually worked with them yet (since the MS compiler doesn't have
them yet except in a beta) I can't be 100% sure but it seems to me that
most objects we'll develop in the new C++ will still generally not use
move semantics.
 
N

Noah Roberts

Juha said:
Which uses raw pointers, not smart ones.

First off, note the wording of the standard being discussed:

"Never ever use a raw pointer when a smart pointer can do the same job."

Demanding that someone use a smart pointer where it supposedly can't do
the job (it can) to counter such a standard is a straw man.

Second off, note that no raw pointer is exposed by std::list.
std::vector *might* expose a pointer but it does so in such a way that
it's actually smarter than your average raw pointer. It has traits
attached to it, turning it into an iterator (a smart pointer). The
std::list template cannot do this.

When a smart pointer can't do the job (or isn't the best way),
sufficient encapsulation can render its bite negligible.
 
N

Noah Roberts

Hicham said:
... my colleague says.

I disagree on the "Never".

Opinions about this "rule of thumb" welcome.

regards,

I think there's some misunderstanding of what a standard like this
should look like by some posting and maybe by your boss as well. It is
'generally' impossible to use smart pointers for everything and not
desired all the time either.

For example, try implementing a smart pointer without a raw pointer.
Sounds pretty stupid, doesn't it. Nieminen makes a fairly similar
demand though for it's a logical end of "never". Although a linked list
can be made with smart pointers, it may not be the best approach. The
more important thing is to abstract the list in such a manner that you
never know or care that it's using pointers inside...or not.

A better standard might tell you to not pass raw pointers out of any
public interface and to prefer smart pointers internally as well. This
allows the developer to use raw pointers internally when they just make
the most sense but then none of this is exposed, which is where most
problems start.

The purpose of OO is to encapsulate. Sometimes what you are
encapsulating is resource management. Sometimes what you are
encapsulating is an ugliness you don't want to infect the rest of the
program. Raw pointers can fit into either category. This is also
Steinbach's point when he says the correct answer is an interface that
ensures a call do finalize(). The fact that there's a raw pointer deep
inside some object or behind some interface is not important if the
interface encapsulates this fact.

Still, a good standard might say to "prefer" smart pointers because
they're just plain easier to work with in many cases. They can take
care of bugs you might not even consider that would be otherwise
difficult to debug, such as a possible memory leak when a client abuses
the interface.
 
J

James Kanze

[.,..]
Very few cases when that isn't completely appropriate for that
need.

Implementing std::vector:). Or ::eek:perator new.

There are very few cases where raw memory is relevant at all,
and all of the ones I know are at such low levels that
std::vector wouldn't necessarily be available.

As I see it:

-- At the application level, you're not doing that much dynamic
allocation anyway, and when you do, it's generally because
the object has a specific lifetime, so smart pointers (at
least the usual ones) aren't all that useful. Most, if not
all, pointers are for navigation only, and raw pointers do
that job very well.

-- At the lowest level, you're implementing the smart pointers,
or even something below them (a garbage collector,
std::vector, etc.), so you almost have to use raw pointers.
You need to start somewhere.

There's a lot of room between those two levels, however.
 
J

James Kanze

There's all kinds of smart pointers.

Agreed. I'll often end up using three or four different types
in special cases in my applications. Regretfully, mention smart
pointers, and 99% of the people seem to imagine
boost::shared_ptr, which is the least useful of all that I've
seen.
 
J

James Kanze

James said:
On 13 Aug, 17:55, Noah Roberts <[email protected]> wrote:
[...]
There are exceptions to any standard.
not in my universe
The very first rule in any coding guidelines should always be
that any rule in the guidelines can be violated, provided the
programmer can justify the violation.
... and get buy in from the team.

I'm not sure whether you mean for the rules, or for the instance
which breaks them, but I totally agree in both cases. In the
end, it's the team which decides, because it's the team which
has to live with the results.
Then everything about why it was violated needs complete
documentation for later reference.

Agreed, but that's part of a more general rule: anything that is
unexpected or unusual need extra documentation, stating why the
expected or usual solution wasn't used.
 
J

James Kanze

"Never ever use a raw pointer when a smart pointer can do the
same job"
<snip>
[...] I use raw pointer when I need to use a raw pointer
and I use a smart pointer when I need to use a smart
pointer.
What purpose does a raw pointer serve?
Navigation.
Almost any case when you need a heap allocated data blob
can be solved by the use of raii objects such as
std::vector or wrapping such allocations in a smart
pointer such as scoped_ptr, shared_ptr, or even
std::auto_ptr.
That's not the case in any of the applications I've worked
on. At the application level, lifetime is generally very
deterministic; objects are created and deleted as a result
of specific input from external sources. Using a
scoped_ptr, shared_ptr or an auto_ptr on any of these will
result in a double delete. (It's sometimes useful to use an
auto_ptr during the actual construction, until the
transaction has completed, or the object has been registered
wherever it has to be registered.)
what about sub-objects or resources? The high level object may
have a deterministic lifetime but the stuff it accumlates
during its lifetime has to be destroyed at the same time. Is
that a justification for a smart pointer or should you just
write your destructors correctly?

Why use a pointer at all. Stuff that belongs to the object
should usually be a member, directly, or a standard collection.
There are a few exceptions, e.g. when using the strategy pattern
internally; in those cases, if the smart pointer is handy, and
you don't have to expose it in the interface, why not.
yes! I'd noticed that. Calls in a communications system don't
vanish at the end of a block!

And they do vanish when the parties hang-up. Even if other
objects still have pointers to them.
my applications are pseudo-embedded; they very much respond to
events that are not determined by the scope structure of the
program.

Well, I was actually asking Noah, because his applications do
seem to have objects whose lifetime is determined by the scope
structure of the program, but which still have to be allocated
dynamically. My experience is more or less limited to telecoms
(pseudo-embedded, if you like---network management, mainly),
large scale business servers and GUI interfaces, and in those
fields, at least, you really have to manage lifetime explicitly.
It's part of the specification, more or less.
 

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,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top