• Advertisement
Sign in to follow this  

References or Pointers. Which syntax do you prefer?

This topic is 1370 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ive always preferred pointers over references. By using pointers when I look at the code its clear which variable holds a memory adress and which is an actual object. ...you look at a pointer, you now that its one. Now the advantages of references are becoming more important to me than this.

So... if you only consider the syntax, which do you prefer? References or pointers.

 

Share this post


Link to post
Share on other sites
Advertisement
It's not really a case of preference. Some things require references, some require pointers.

As out params to methods, I work with libraries that use pointers as it is clear at the point of call that a param is an out param which is reasonable but unnecessary in modern IDEs.

Where I have a choice, I prefer a reference unless the value can be maybe null.

Share this post


Link to post
Share on other sites

I tend to prefer by reference when possible, but pointers are advantageous when you can have a null value.  References can "force" the existence of a value (there are exceptions to this).

Edited by Rattrap

Share this post


Link to post
Share on other sites

Pointers are optional or are expected to change during their lifetimes, references are non-optional and won't change.

 

If during the lifetime of your pointer, it isn't expected to change and it's not optional - it should probably be a reference.

Share this post


Link to post
Share on other sites

Where I have a choice, I prefer a reference unless the value can be maybe null.


I've taken a more "modern" approach here and prefer always using reference with a concept of Optional types if I need something nullable. Similar to the proposed C++ std::optional.

The primary difference between a reference and a pointer is that a pointer can be rebound (you can point it at something else) while a reference cannot. A reference is an alias to an object instance while a pointer is an entirely different concept (it is its own value representing a memory address). As you (almost) never actually need rebinding, you likewise almost never need pointers and can just use references most of the time. Optional types let you return a nullary value when appropriate, be it a reference (or some other non-copyable/non-moveable handle), a primitive like an int, or so on, with a unified interface for nullary checking/handling.
 
// a short alias
template <typename T>
using opt = std::experimental::optional<T>;

// function using optional to return either a reference or 'null'
Bar g_Bar;
opt<Bar&> foo() { return condition ? g_Bar : nullopt; }

auto bar = foo();
if (bar)
  use_bar(*bar);
It'd be nice if the syntax were a little cleaner in C++, but then it'd also be nice if owning pointers (std::unique_ptr) were a built-in feature like in Rust. C++ is not for people afraid of typing or syntax spew. tongue.png Edited by SeanMiddleditch

Share this post


Link to post
Share on other sites

Thanks for the answers so far!

 


A reference is an alias to an object instance while a pointer is an entirely different concept (it is its own value representing a memory address)

Thats the thing... that they are a different concept and use different syntax. (But generally they are handled the same way in the machine code.) In the case of the pointer its obvious that you dont operate on a "real object" but with a reference it isnt.

Share this post


Link to post
Share on other sites
A common style that I've seen is const-references for in-params (which should act like pass-by-value, but are expensive to copy) and pointers for all out params.
The rationale behind this is that it makes out-params very obvious at the call-site, similarly where in other languages the caller must use an 'out' keyword.

It'd be nice if the syntax were a little cleaner in C++, but then it'd also be nice if owning pointers (std::unique_ptr) were a built-in feature like in Rust. C++ is not for people afraid of typing or syntax spew. :P

That would completely kill C++ as being a pay-for-what-you-use / opt-in language. Most embedded systems that I've worked on still use raw-pointers, or a smart pointer that acts like a raw one, but only performs leak detection during development.

Share this post


Link to post
Share on other sites

That would completely kill C++ as being a pay-for-what-you-use / opt-in language. Most embedded systems that I've worked on still use raw-pointers, or a smart pointer that acts like a raw one, but only performs leak detection during development.


Owned pointers are a language semantic that have _zero_ overhead compared to manually using raw pointers and new/delete in the requisite places. Raw pointers would of course stay around for cases you don't want owned semantics. All an owned pointer does is call delete (or with std::unique_ptr any deleter policy function specified in the template) when the pointer goes out of scope. std::unique_ptr takes up the same space (assuming your deleter policy doesn't require extra space), has no additional runtime overhead (assuming optimizations are on and all the template goo gets inlined and removed), and imposes no additional memory allocation requirements beyond whatever your allocator uses. You can use unique_ptr with pooled objects trivially by allocating from the pool and using a release_to_pool deleter policy instead of the default policy. Reference-counted pointers, GC pointers, and other shared-ownership pointers are a different thing.

Share this post


Link to post
Share on other sites
Yeah true, as long as you introduced new syntax, like C++CLR did with it's "Foo^ foo" type pointers.
I was thinking of the consequences of making &/* do any more than they do currently.

Share this post


Link to post
Share on other sites

 

That would completely kill C++ as being a pay-for-what-you-use / opt-in language. Most embedded systems that I've worked on still use raw-pointers, or a smart pointer that acts like a raw one, but only performs leak detection during development.


Owned pointers are a language semantic that have _zero_ overhead compared to manually using raw pointers and new/delete in the requisite places. Raw pointers would of course stay around for cases you don't want owned semantics. All an owned pointer does is call delete (or with std::unique_ptr any deleter policy function specified in the template) when the pointer goes out of scope. std::unique_ptr takes up the same space (assuming your deleter policy doesn't require extra space), has no additional runtime overhead (assuming optimizations are on and all the template goo gets inlined and removed), and imposes no additional memory allocation requirements beyond whatever your allocator uses. You can use unique_ptr with pooled objects trivially by allocating from the pool and using a release_to_pool deleter policy instead of the default policy. Reference-counted pointers, GC pointers, and other shared-ownership pointers are a different thing.

 

He referred to embedded systems where tools haven't matured and probably never will (because the specs keep changing all the time or nobody is actually bothering); and thus the assumption you mentioned "optimizations are on and all the template goo gets inlined and removed" cannot be made.

Furthermore, we're talking about video games here, and it gets hard to debug when your game runs at 1 fps because the compiler didn't fix the smart pointer's overhead.

Also smart pointers DO incur into overhead when used in more advanced cases (i.e. inside a struct hold by a vector inside a vector). This overhead may be possible to remove with C++11 (move semantics) but that's bleeding edge C++ that is still not as optimized as it can be; and not everybody has the possibility to use C++11 and must stick with older versions.

Share this post


Link to post
Share on other sites
Should pretty much always prefer to use references for passing information unless you specifically have need of a pointer.

The only time I really make an exception to that is if I feel the information is not clear. I.e. I usually don't use a reference parameter for a function if the function is going to delete the passed object when it is done with it, in that case I'd usually make it a pointer with a null check.

Performance shouldn't even really be a concern in terms of pointers and references anyway, their main strength is conveying information and slapping people's hands a bit if they try and do something horribly wrong.

Share this post


Link to post
Share on other sites

Should pretty much always prefer to use references for passing information unless you specifically have need of a pointer.

The only time I really make an exception to that is if I feel the information is not clear. I.e. I usually don't use a reference parameter for a function if the function is going to delete the passed object when it is done with it, in that case I'd usually make it a pointer with a null check.

Performance shouldn't even really be a concern in terms of pointers and references anyway, their main strength is conveying information and slapping people's hands a bit if they try and do something horribly wrong.

Pointers are loaded guns. Sometimes you need a gun. But in most cases what you really needed was a hammer, so check to make sure you really need that gun before you start using it as a hammer.

Share this post


Link to post
Share on other sites
  • Foo *myFoo. This could be an output variable. Or could be that you need to write to raw memory (i.e. gpu pointer) and/or do some pointer addressing math. Could be null (you may not be always expected to modify it). Could also be input. The most ambiguous of all.

 

So it is required to have write access to a GPU pointer?

Share this post


Link to post
Share on other sites

 

  • Foo *myFoo. This could be an output variable. Or could be that you need to write to raw memory (i.e. gpu pointer) and/or do some pointer addressing math. Could be null (you may not be always expected to modify it). Could also be input. The most ambiguous of all.

 

So it is required to have write access to a GPU pointer?

 

? I did not understand the question.

Pointers can map to almost anything through virtual addressing. Normally it is mapped to system RAM; but a pointer can also use an address mapped to physical hardware in order to communicate with it (a GPU, a sound card, an ethernet card, etc).

There are no requirements for "Foo *myFoo"; and that's the thing. Foo *myFoo could be used for almost anything (input, output, putting stuff on ram, self-modify instructions, talking to hardware mapped devices, etc), and you will have to rely on the documentation to tell you what's the pointer going to be used for. If you can use the other three variants, prefer those first.

Share this post


Link to post
Share on other sites

You should use references over pointers wherever possible.

When you see a pointer as a method parameter, it should tell you that either it is a C import e.g. API function, or that it needs to be given the address or an array of values rather than a single value, or that NULL is an acceptable thing to pass in.

Share this post


Link to post
Share on other sites

I tend to prefer references over pointers when there's no compelling reason requiring me to use one or the other (e.g. null values).  the big reason is that most C++ programmers still use pointers for their array types.  References, on the other hand, entail strictly reference semantics, without any other extra baggage.  Granted, "reference semantics" carry some semantic baggage of their own (are we talking in, out, or inout parameters?) but it's a strict subset of pointers.  The more I can reason about code at a glance, the better.

Edited by SeraphLance

Share this post


Link to post
Share on other sites

A common style that I've seen is const-references for in-params (which should act like pass-by-value, but are expensive to copy) and pointers for all out params.
The rationale behind this is that it makes out-params very obvious at the call-site, similarly where in other languages the caller must use an 'out' keyword.
 

It'd be nice if the syntax were a little cleaner in C++, but then it'd also be nice if owning pointers (std::unique_ptr) were a built-in feature like in Rust. C++ is not for people afraid of typing or syntax spew. tongue.png

That would completely kill C++ as being a pay-for-what-you-use / opt-in language. Most embedded systems that I've worked on still use raw-pointers, or a smart pointer that acts like a raw one, but only performs leak detection during development.

 

Is a valid use of raw pointers for things within a class?  For example, if I construct an object (and correctly release resources if the object fails to construct) and then provide the correct release in the destructor would this be a valid use?  I guess that's the RAII paradigm in a nutshell.  Or would you advocate smart pointers even in this scenario?

 

I suppose if I need to share a dynamic resource to things outside of the raw pointer containing class then smart pointers become essential.  But even general purpose libraries intended for mass consumption avoid smart pointers because there's no hard bound guarantee that the same smart point convention is followed by programs which use different implementations.  How then is the problem of detecting resource leaks handled?  Is there a method to the madness or are designs/tools up to the task?

 

If there's any doubt, these are genuine questions and not quibbles with the concept of smart pointers.  I just haven't been on a large enough project to see what disasters might befall the unwary.

Share this post


Link to post
Share on other sites


Is a valid use of raw pointers for things within a class?  For example, if I construct an object (and correctly release resources if the object fails to construct) and then provide the correct release in the destructor would this be a valid use?  I guess that's the RAII paradigm in a nutshell.  Or would you advocate smart pointers even in this scenario?

 

Using the right smart pointer doesn't cost you anything -- either you need shared ownership and needed a reference-counting pointer anyway, or you have singular ownership and unique_ptr provides exactly that at no cost over raw pointers, but in an absolutely correct, secure, and exception-safe manner. It also provides additional clarity to any code that touches the pointer -- you don't know whether a raw-pointer expresses ownership or not, or whether its shared, etc. With unique_ptr, the type tells you the precise ownership semantics.
 

I suppose if I need to share a dynamic resource to things outside of the raw pointer containing class then smart pointers become essential.  But even general purpose libraries intended for mass consumption avoid smart pointers because there's no hard bound guarantee that the same smart point convention is followed by programs which use different implementations.  How then is the problem of detecting resource leaks handled?  Is there a method to the madness or are designs/tools up to the task?

 

Still prefer smart pointers for the reasons above. In particular, even if your RAII-aware object doesn't leak under normal construction/deconstruction, have you also taken the additional steps to ensure that it doesn't leak under exceptions that might occur mid-construction? Have you correctly implemented move semantics? 

 

Regarding other libraries avoiding smart pointers, or having their own versions of them, that's more of a reflection that C++ didn't have standard smart pointers until C++11 (or was it TR1?) -- there was auto_pointer, which was supposed to do the same thing that unique_ptr does today, but they got the design wrong and so it was broken.

 

Regarding detecting resource leaks, if you use the smart pointers, the only real 'leak' you have to worry about is cyclical references between shared_ptrs, which is precisely why weak_ptr exists.
 

If there's any doubt, these are genuine questions and not quibbles with the concept of smart pointers.  I just haven't been on a large enough project to see what disasters might befall the unwary.

 

What I hear you saying is that you've never been on a project large enough that manually-managing memory allocation and deallocation has bitten you. That may be true; its easy to get your head around small projects with simple and clear object lifetimes. However, that doesn't mean that the smart pointers are somehow overwrought for the job -- programmers have a tendency to distrust code they didn't write themselves, but its always more dangerous to roll your own.

 

I also suspect that you underestimate how truly wary one must be in the face of exceptions (as just one example) -- its not enough to simply have matching pairs of new and delete in the constructor and destructor -- that, alone, will (not might) leak. To be leak-free in the face of exceptions, you need to be completely aware of the exact state of the partially-constructed objects at all points during construction and destruction, including inherited-from objects. Then, you need to be sure that some other programmer isn't going to come in behind you and move or introduce statements in your carefully-ordered code, which could break its ability to deal with exceptions correctly.

 

Just use the smart pointers, and all of this worry goes away so you can stop wrestling details and get on to code you care about.

Share this post


Link to post
Share on other sites

For sure, I plan to make habitual use of them.  I've just read a good deal of RAII which has been touted as 'sufficient' in the face resource leaks.  Perhaps that's all it is, sufficient, but not exemplary.

 

That's a good point, unique_ptr confers an expected meaning of how the pointer is to be used.  Barring a comment, a naked pointer doesn't say anything about its expected purpose.

 

While I haven't been on a large project, one that I'm currently working on is coming close.  Relationships are established such that it's important to keep track of ownership issues.  I am vaguely aware that the system is complex enough, that despite my best intentions, I'm going to miss some critical step that will cause future headaches.

 

A (newbie) usage question:  

 

Suppose I have a base class and several derived classes.  I maintain a vector of Base (abstract) objects and one for each Derived type objects.  The Base vector is a superset of all the Derived vectors.  A factory creates each derived object and I add it to the Base vector and the appropriate derived vector.  Where should unique_ptr/shared_ptr/etc. live in this scheme?  My inexperienced guess is that it lives between factory (allocation) and storage on the vectors after which point it is moved onto the vector.  Or should it be something different?

 

BTW, thanks Servant and Ravyne.

Share this post


Link to post
Share on other sites

Is a valid use of raw pointers for things within a class?  For example, if I construct an object (and correctly release resources if the object fails to construct) and then provide the correct release in the destructor would this be a valid use?

The modern/official/standardized C++ standard smart pointers were only added a few years ago, so there's decades of older software that's been written without them ;P

In regards to constructors that can fail - every single commercial C++ program that I've worked on for the past decade has *disabled exceptions completely*! This means that I've never had to worry about half-constructed objects except as an academic exercise. YMMV.

N.B. I would certainly advocate the use of unique/shared/weak_ptr instead of raw pointers, in general, because this is the common modern C++ style.

There still plenty of projects that use older styles though, and when in Rome, you do as the Romans do.
If the project is run by people who are plain old C veterans, then they'll be very comfortable with raw memory management, expressing ownerships/lifetimes through documentation instead of through template magic.

On my project, I use scope/stack allocation instead of new/delete, so all objects are stack allocated. This is a completely different paradigm with completely different ownership/lifetime semantics - e.g. It makes no sense to say that one object owns another, instead, you can say that one object's lifetime is nested within another's lifetime.
Under this paradigm I personally have no qualms using raw pointers for everything.
 

But even general purpose libraries intended for mass consumption avoid smart pointers because there's no hard bound guarantee that the same smart point convention is followed by programs which use different implementations.

Yeah, many game engines either don't use smart pointers, or have reinvented their own versions of them. This makes the job of a middleware vendor quite difficult, because all your clients have their own particular requirements. The easiest option is often to cater to the lowest common denominator, which is a C API :lol:

How then is the problem of detecting resource leaks handled?

there's external tools that you can use, plus most game engines will log every call to new/delete/malloc/free to a file (along with the filename+line number of the code that calls it), which can be analyzed later to detect errors.

Share this post


Link to post
Share on other sites

For sure, I plan to make habitual use of them.  I've just read a good deal of RAII which has been touted as 'sufficient' in the face resource leaks.  Perhaps that's all it is, sufficient, but not exemplary.

 

...

 

Suppose I have a base class and several derived classes.  I maintain a vector of Base (abstract) objects and one for each Derived type objects.  The Base vector is a superset of all the Derived vectors.  A factory creates each derived object and I add it to the Base vector and the appropriate derived vector.  Where should unique_ptr/shared_ptr/etc. live in this scheme?  My inexperienced guess is that it lives between factory (allocation) and storage on the vectors after which point it is moved onto the vector.  Or should it be something different?

 

RAII is the correct idiom, but the way its implemented has changed. Before having a good stable of smart pointers, you had to implement RAII with raw pointers, matched pairs of new/delete, and careful consideration of exception behavior. Now we have them -- they don't replace RAII, they make it easier and lighter-weight to implement them. As an almost fringe benefit, the smart pointers are so useful that they can sometimes simply stand on their own.

 

The way to think about the smart pointers is that they hoist objects with dynamic storage duration into the system of static, thread (C++11), or automatic storage durations, which are easier to think about. RAII does the same by encapsulating lifetime of a resource into an object, and then letting that object live (potentially) with static, thread or automatic storage duration.

 

In your example, you can approach it in a few ways, but I think the best way is to have the per-derived-type concrete containers own those objects (that is, it could simply be a vector of that type), and then I would re-build the super-list each frame (or whatever) after the objects have settled, and the super list can simply be non-owning raw pointers. This will be robust even if the other containers grow and re-allocate between frames, while still maintaining contiguous memory access patterns. Use emplace_back to fill the containers while avoiding extraneous copies and potential memory leaks.

 

But that approach only works if the per-type containers logically own the objects. Otherwise, if the object lifetimes are not owned by those per-type containers, then you probably want the per-type containers to hold their objects as a shared_ptr, and the super-list to be either a shared_ptr as well (if the usefullness of the object might live longer in the super list than on the per-type list), or a weak_ptr (in which case, you would want to cull weak pointers that no longer refer to a valid object before iterating over the super-list.)

 

This sounds, actually, like it might be a job for a pooled allocator, depending on what exactly you hope to achieve.

Share this post


Link to post
Share on other sites

The implementation of RAII hasn't changed: A class is RAII if all the member variables are RAII. Since smart pointers are RAII, this fits in fine with the existing concept of RAII. std::fstream is RAII, for example, and classes that have std::fstream as members are RAII (if the class's other members are also RAII).

 

RAII doesn't mean the class has to manually or personally manage the memory. It just means the memory (and other resources) have to ultimately be managed without the person using the class needing to manage it. Which means member variables can (and should!) manage their own memory.

 

Smart pointers fit in with the existing concept of RAII without changing it.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement