Jump to content

  • Log In with Google      Sign In   
  • Create Account

References or Pointers. Which syntax do you prefer?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
25 replies to this topic

#1 Aliii   Members   -  Reputation: 1448

Like
0Likes
Like

Posted 18 April 2014 - 01:10 PM

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.

 



Sponsor:

#2 Aardvajk   Crossbones+   -  Reputation: 6218

Like
4Likes
Like

Posted 18 April 2014 - 01:19 PM

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.

#3 Rattrap   Members   -  Reputation: 1788

Like
0Likes
Like

Posted 18 April 2014 - 01:20 PM

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, 18 April 2014 - 01:21 PM.


#4 Matias Goldberg   Crossbones+   -  Reputation: 3698

Like
13Likes
Like

Posted 18 April 2014 - 01:35 PM

It's not just personal taste. Reference & Pointers tell you about the code.

  • const Foo &myFoo means this is an input variable, which cannot be null. Probably it's a large struct and we're using reference to avoid passing by value. If the reference is actually null, something really bad happened before entering the function. GDB & MSVC both support showing the address (just print &myFoo)
  • const Foo *myFoo means this is an input variable, but the pointer may be null. i.e. optional. Check the documentation to see if you can assume it cannot.
  • Foo &myFoo. This is an output variable. You're expected to modify it. Could also be input, but this is discouraged for many reasons.
  • 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.

Additionally, pointers can have a few more qualifiers that sadly references do not, such as __restrict, which is very powerful in code optimization.


Edited by Matias Goldberg, 18 April 2014 - 01:38 PM.


#5 Servant of the Lord   Crossbones+   -  Reputation: 21073

Like
0Likes
Like

Posted 18 April 2014 - 01:40 PM

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.


It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#6 SeanMiddleditch   Members   -  Reputation: 7192

Like
4Likes
Like

Posted 18 April 2014 - 04:23 PM

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, 18 April 2014 - 05:05 PM.


#7 Aliii   Members   -  Reputation: 1448

Like
0Likes
Like

Posted 18 April 2014 - 05:15 PM

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.



#8 Hodgman   Moderators   -  Reputation: 31852

Like
4Likes
Like

Posted 18 April 2014 - 05:36 PM

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.

#9 SeanMiddleditch   Members   -  Reputation: 7192

Like
0Likes
Like

Posted 18 April 2014 - 06:20 PM

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.

#10 Hodgman   Moderators   -  Reputation: 31852

Like
0Likes
Like

Posted 18 April 2014 - 06:23 PM

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.

#11 Matias Goldberg   Crossbones+   -  Reputation: 3698

Like
0Likes
Like

Posted 18 April 2014 - 06:36 PM

 

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.



#12 Satharis   Members   -  Reputation: 1264

Like
0Likes
Like

Posted 18 April 2014 - 06:46 PM

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.

#13 ikarth   Members   -  Reputation: 445

Like
0Likes
Like

Posted 18 April 2014 - 07:55 PM

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.



#14 gasto   Members   -  Reputation: 254

Like
0Likes
Like

Posted 18 April 2014 - 09:38 PM

  • 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?


Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

#15 Matias Goldberg   Crossbones+   -  Reputation: 3698

Like
1Likes
Like

Posted 19 April 2014 - 12:00 PM

 

  • 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.



#16 iMalc   Crossbones+   -  Reputation: 2314

Like
0Likes
Like

Posted 20 April 2014 - 10:46 PM

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.


"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

#17 SeraphLance   Members   -  Reputation: 1455

Like
3Likes
Like

Posted 22 April 2014 - 03:57 PM

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, 22 April 2014 - 03:57 PM.


#18 Ravyne   GDNet+   -  Reputation: 8165

Like
5Likes
Like

Posted 22 April 2014 - 04:32 PM

Its not a matter of preferential syntax, its a matter of using the right tool for the job. In practice, if you're doing it right, you should see a preponderance of value types (neither references nor pointers), followed by references, then smart pointers with ownership semantics (shared_ptr, unique_ptr), and then by raw pointers where there are no ownership semantics. Likewise, if you're doing it right, you'll see much more make_shared and make_unique than you will new and delete. You'll probably see a smattering of weak_ptr too.

 

If you take full advantage of C++11 smart pointers, you should only have raw pointers which refer to, but do not own, the object at their address, and that object will either have static or automatic storage duration (its either preallocated or on the stack) that determines its ownership and lifetime explicitly, or that object will have dynamic storage duration and be allocated via make_shared, make_unique, or be managed by some container, and its ownership and lifetime is defined further up the call-stack. new and delete are now code smells, they might still be necessary in specialized use-cases, but they should be viewed with extreme suspicion and prejudice.

 

Its easy to go crazy and start using smart-pointers everywhere though. However, its not necessary or even good practice. For example, it can harm performance to pass smart pointers down longish-chains of function calls, and it doesn't buy you anything -- a smart-pointer only needs to go as far down the call-stack as is necessary to potentially transfer its ownership -- a parent function-call having ownership of the object implies that the object still exists for child function-calls, so you don't need to pass them a smart pointer to prove it.


Edited by Ravyne, 22 April 2014 - 04:40 PM.


#19 Cosmic314   Members   -  Reputation: 1261

Like
3Likes
Like

Posted 23 April 2014 - 09:20 AM

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.



#20 Servant of the Lord   Crossbones+   -  Reputation: 21073

Like
5Likes
Like

Posted 23 April 2014 - 12:14 PM

I don't work on large projects - just my own small game project, so take this with a grain of salt.
 

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?

The thing is, std::unique_ptr wraps all that for you. It's not just a matter of a destructor - you also need copy operators, assignment operators, move operators, and move-assignment operators.

std::unique_ptr has zero overhead in size and de-reference speed as a normal pointer (when compiler optimizations are properly turned on).
std::unique_ptr doesn't permit copying - so you'd still have to implement a copy-constructor and do an explicit copy, but you'd've had to do that anyway.

There are some situations where new and delete should still be used, but those are few and far between.

Raw pointers are also fine when they don't *own* the memory, and when the lifetime of the memory is guaranteed to outlast the raw pointer. So raw pointers shouldn't be *rare*, just less common than previously.
 

I suppose if I need to share a dynamic resource to things outside of the raw pointer containing class then smart pointers become essential.

Depends what you mean by 'share'. Just return a reference (usually const) if you mean 'shared access'. If you mean 'shared ownership' of the memory, then yes, std::shared_ptrs are a likely choice - but it'd depend on the situation. If you want to share access, but you aren't sure if the memory will outlast the pointer/reference, than std::shared_ptr for the owner(s), and std::weak_ptrs for the accessor(s).
 

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.

I'm not exactly sure what you mean here. Are you saying, different implementations of custom smart pointer classes, or are you saying different implementations of standard library smart pointers?

If the former, the solution is to use standard library pointers. If the latter, the solution is that you should be building those third-party libs from source code anyway, because who knows what compiler settings they used, and all your code should be compiled with the same compiler settings regardless of whether smart pointers were used.
It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS