References or Pointers. Which syntax do you prefer?

Started by
24 comments, last by SeraphLance 9 years, 11 months ago

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.

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

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.

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

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

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

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.

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.

throw table_exception("(? ???)? ? ???");

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.

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.

This topic is closed to new replies.

Advertisement