References or Pointers. Which syntax do you prefer?

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


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.

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

Advertisement

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.

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.

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.

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

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.

Interestingly enough, in the land of smart pointers (except, as Hodgman put it, when you're "in Rome"), any time I ever need new, it's for the placement variety, because that's one space where RAII smart pointers obviously just don't jive.

This is slightly off-topic from the whole Reference/Pointer thing though.

This topic is closed to new replies.

Advertisement