4

I like the new pointer types in C++11, but sometimes I still need a raw pointer. Something that makes me increasingly sad about "raw" types in C++, however, is their habit of initializing as undefined when not given an explicit value. As I use std::shared_ptr<> and the like more often, this need to initialize raw pointers to null feels increasingly brittle and unnecessary. I'm talking about:

class foo
{
    ...
    std::shared_ptr< bar > pb;   // Initially null in whatever constructor.
    std::unique_ptr< dar > pd;   // Likewise.
    std::weak_ptr< gar > pg;     // And again.
    lar* pr;                     // Uh-oh! Who knows what this is? Better remember to initialize...
};

foo::foo( int j )
: pr( nullptr )
{...}

foo::foo( const string& s )
: pr( nullptr )
{...}

... etc.: many tedious and error-prone constructor definitions follow.

What I'd like, therefore, is a "raw pointer with null initialization." Something like:

class foo
{
    ...
    std::shared_ptr< bar > pb;   // Initially null in whatever constructor.
    std::unique_ptr< dar > pd;   // Likewise.
    std::weak_ptr< gar > pg;     // And again.
    raw_ptr< lar > pr;           // Once more with feeling.
};

foo::foo( int j )
{...}                            // No explicit pointer initialization necessary.

foo::foo( const string& s )
{...}

...

More precisely, what I want is a simple, cheap type that acts exactly like a raw pointer in every way except that its default constructor initializes it to nullptr.

My question: (1) Does such a thing already exist in the standard library? (2) If not, what would be the most elegant/smallest way to accomplish an implementation of this type?

P.S. I'm not interested in Boost or any other libraries, unless perhaps it is a header-only library in a single file. Smallness and simplicity are of the essence.

OldPeculier
  • 11,049
  • 13
  • 50
  • 76
  • 9
    You could just initialize it in the class: `lar *pr{};`. – chris Jul 08 '13 at 19:15
  • Raw pointers should only be used in private. Since you're probably writing class constructors anyway, it shouldn't be too much of a leap to add the initializer... And otherwise you should keep your scopes tight, so you should try to initialize variables meaningfully at the point of declaration. – Kerrek SB Jul 08 '13 at 19:16
  • @KerrekSB You would think not, but we find that failure to remember to explicitly initialize members is one of the most common sources of really insidious, hard-to-find errors, made worse when members get added and removed over time and when more than one constructor is involved. – OldPeculier Jul 08 '13 at 19:20
  • 1
    @OldPeculier: Compilers can usually warn you about non-initialized members, so this is moderately easy to patrol... but you're right in general, of course. I suppose classes *shouldn't* grow that much, because that's a symptom of mixing responsibilities. In particular, if a class contains raw pointers, it probably shouldn't have many other concerns. – Kerrek SB Jul 08 '13 at 19:21
  • 3
    This looks like a *how do you make the best use of the wrong tool?* type of question. First you need to clarify why the solution to your real problem is using a raw pointer. Unless that is justified, considering the fragility of not initializing it is a non question. – David Rodríguez - dribeas Jul 08 '13 at 19:22
  • 2
    @DavidRodríguez-dribeas Come now. Raw pointers still have a valid place. I don't think the use of raw pointers needs rationalizing. Given that, they clearly differ from the "modern" pointers in their need for explicit initialization. I think those facts alone warrant the question. – OldPeculier Jul 08 '13 at 19:25
  • @OldPeculier Raw pointers still have a valid place ... when you're writing a class to manage a resource. But such a class shouldn't be dealing with a bunch of other responsibilities, like the one you've posted in your question. If you must have something mimicking a raw pointer, create a `lar_ptr` similar to the one in Mark's answer below. Then use that type wherever you're using `lar *` right now. Also, a `unique_ptr`, with a custom deleter, is more than sufficient for handling most resource handling needs. – Praetorian Jul 08 '13 at 19:36
  • 4
    @OldPeculier: On the contrary, raw pointers should be the exception and being the exception should be in use in only a few components that need to be treated with extra care. If you are dealing with this type of special components and you are really paying extra care chances are that you will not forget initialization. The problem comes when you decide that raw pointers are common and, used to them, you become more careless. In a world were raw pointers are rare, code with raw pointers is already suspicious and less likely to have errors that bypass review. – David Rodríguez - dribeas Jul 08 '13 at 19:37
  • 1
    If you can't remember to initialize the raw pointer to zero, how are you going to remember to use the raw_ptr<> class you create to solve the problem? – jmucchiello Jul 08 '13 at 19:44
  • @jmucchiello I think your question strikes at the heart of a fundamental concept. Remembering to use a certain type when declaring a member is much easier—people are much more reliable at it—than remembering to initialize the value of that type in distinct constructors. Inline initialization within the class helps defeat that problem, but is not available in, e.g., Visual Studio 2012, and is unavailable under certain other circumstances. – OldPeculier Jul 08 '13 at 20:13
  • 1
    But you've just increased the maintenance cost of your entire system by introducing an unfamiliar element. New programmers seeing your code for the first time will need to be educated as to the use of this mostly_raw_ptr and what it does. Failing to initialize member variables is what code reviews are for. They are just such a fundamental mistake that they bring to mind the aphorism: it's impossible to make anything foolproof, fools are too ingenious. I guarantee with 30 days of implementing this for the first time, you have a dozen bugs relating to "missing" features of your raw_ptr class. – jmucchiello Jul 08 '13 at 20:41
  • 4
    @jmucchiello Good grief. Is a question like this really the right place to discuss the philosophy of software design and C++ usage? People use C++ in all sorts of ways. Live and let live! Please, just assume for the purposes of this question that some C++ programmers sometimes have good reasons to use raw pointers and to want them automatically nulled without recourse to in-class or in-constructor initialization. – OldPeculier Jul 08 '13 at 21:48
  • 1
    @KerrekSB: "Raw pointers should only be used in private". Imo, this is not a good advice. There are many ways to use C++, and many programming styles. If in your style/situation using raw pointers is a bad idea, it doesn't mean same will apply to other styles. I worked on a project once where programmers were forbidden to use STL, exceptions, and allocating memory dynamically was frowned upon. For project lead that made sense. – SigTerm Jul 09 '13 at 00:43
  • @SigTerm It's not just a question of style; it is very difficult to write exception safe code when using raw pointers. Of course there are environments where dynamic memory allocation and exceptions are forbidden, but that is certainly not the norm. In the general case, where such issues are not a concern, good, idiomatic C++ will avoid using raw owning pointers. – Praetorian Jul 09 '13 at 03:44
  • @Praetorian: I still disagree, but I'm not in the mood for arguing. Have a nice day. – SigTerm Jul 09 '13 at 04:33

4 Answers4

16

C++11 allows in class initialization of data members.

class foo
{
  // ...
  lar *pr = nullptr;
};

That'll always initialize pr to nullptr unless you assign another value in the constructor.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Yes. It doesn't help in my particular case, but it's a valid answer. See chris's comment above with lar* pr{};—even tidier. – OldPeculier Jul 08 '13 at 19:22
  • @OldPeculier Why doesn't it help in your case? – Praetorian Jul 08 '13 at 19:23
  • Well, it solves the DRY problem, where you only need to say `nullptr` once, at the declaration, rather than repeating it in every constructor. It just doesn't solve you not needing to say it at all. – Joe Z Jul 08 '13 at 19:24
  • @Praetorian It's involved, but these pointers are also caught up in a reflection metadata system that makes it more difficult—not impossible—for me to add in-class data member initializers. – OldPeculier Jul 08 '13 at 19:27
  • 2
    @OldPeculier I'll have to take your word on that one. Irrespective of what these raw pointers are being used for, if they truly are declared as data members of pointer type, I don't get why it's difficult to initialize them to some value. – Praetorian Jul 08 '13 at 19:39
  • @Praetorian I can see why you'd be confused. Have you ever worked with a reflection system for C++? They tend to impose obstacles and complexities. – OldPeculier Jul 08 '13 at 20:15
  • Just because this answer doesn't help with your toolchain, doesn't mean it isn't the right answer for this question in C++. – jmucchiello Jul 08 '13 at 20:59
  • @jmucchiello I can't conceive of any definition of the word "right" that doesn't involve the word "working." Since this answer doesn't work with one of the most widely-used toolchains, I think that other answers have a valid claim to "best." – OldPeculier Jul 08 '13 at 21:54
  • 5
    @OldPeculier, Remove the C++11 tag from the question and I'll agree with you 100%. All compliant C++11 toolchains work with this answer. – jmucchiello Jul 09 '13 at 00:52
9

It looks like you need "World’s Dumbest Smart Pointer" which has been proposed as an addition to a future C++ standard template library. You can see the proposal here: "A Proposal for the World’s Dumbest Smart Pointer" and here: "A Proposal for the World’s Dumbest Smart Pointer, v2" and here: "A Proposal for the World’s Dumbest Smart Pointer, v3"

The proposal contains a potential partial implementation which you may be able to adapt for use with the compiler you are currently using. The implementation is similar to the almost_raw_ptr solution provided by Mark Ransom. A web search for exempt_ptr will give more details.

A proposal for the World’s Dumbest Smart Pointer, v3 has "Renamed exempt_ptr to observer_ptr" see the linked doument for other changes.

ChetS
  • 658
  • 7
  • 15
  • Even better than Mark Ransom's excellent answer, this exactly expresses the need I'm highlighting, discusses a variety of clever potential solutions, and outlines a strong one. Thank you. – OldPeculier Jul 09 '13 at 01:48
  • 1
    I don't see the need for a dumb pointer, we can simply use `std::reference_wrapper`, which is basically the same thing. – Mooing Duck Aug 29 '13 at 17:22
  • 1
    The `exempt_ptr` proposal supports storing `nullptr` and has a default constructor which allows it to fullfill use cases that `std::reference_wrapper` cannot. – ChetS Aug 30 '13 at 20:35
6
template<typename T>
class almost_raw_ptr
{
public:
    almost_raw_ptr(T* p = nullptr) : m_p(p) {}
    T* operator=(T* p) { m_p = p; return p; }
    operator T*() const { return m_p; }
    T* operator->() const { return m_p; }
    T& operator*() const { return *m_p; }
    T& operator[](int i) const { return m_p[i]; }

private:
    T* m_p;
};

This won't work if you need a pointer or a reference to the actual raw pointer, but it should work for everything else.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Nope. Needs comparison operators, copy constructors, and lots of other cruft if we're going to go down that road. – OldPeculier Jul 08 '13 at 19:21
  • Yeah, I imagine things get even more exciting if you want to do pointer arithmetic on one of these things, too. – Joe Z Jul 08 '13 at 19:23
  • A `operator*` would be needed in many contexts... `(*ptr).foo()` – David Rodríguez - dribeas Jul 08 '13 at 19:24
  • 3
    @OldPeculier, the automatic conversions to/from raw pointers will cover most needs for comparisons and copies. – Mark Ransom Jul 08 '13 at 19:29
  • @MarkRansom You were right, I was wrong. Just tested it and it works like a charm. Thanks! – OldPeculier Jul 08 '13 at 19:36
  • IMHO your `operator*` is wrong. Dereferencing a const pointer should yield a nonconst lvalue. That's how naked pointers, `unique_ptr` and `shared_ptr` work. (That goes for `operator->` as well.) – Adam H. Peterson Jul 08 '13 at 19:41
  • @AdamH.Peterson, very good point. I was thinking of the syntax of raw pointers where the `const` attribute accrues to the type pointed at, not the pointer itself. I have an open question on Meta that will need answering before I can fix this. – Mark Ransom Jul 08 '13 at 19:43
  • Your `operator->`, `operator*`, `operator[]`, and `operator T*` functions should be `const`, since it's legal to call them on const instances of the `almost_raw_ptr`. – Adam H. Peterson Jul 08 '13 at 20:42
  • This is a perfect answer as is. Stop exiting it @Mark. The comments below show how incomplete it is and how it will eventually grow into a monstrousity that no one can comprehend and every pointer in your system is saddled with them. Inline expansion is OPTIONAL. So code that says refers to ptr[3] might be a function call instead of a single indexed offset load instruction. Worst Practice! – jmucchiello Jul 08 '13 at 20:46
  • 3
    @jmucchiello, the overhead will be no worse than any other smart pointer type. Inline expansion isn't guaranteed but the standard library templates depend on it for acceptable performance and this template is no different. Look into the definition of `std::vector` and tell me what you find for `operator[]`. – Mark Ransom Jul 08 '13 at 21:15
  • @Mark, It's still a worst practice. Look at the question. You see 4 member variables defined. The fourth one is a raw pointer. Adding =nullptr to the raw ptr is infinitely easier than creating a new class and debugging all the corner cases creating that class creates. Just because the Questioner's tool chain doesn't support =nullptr doesn't mean that isn't the right answer. Yes, the class in question is basically fulfilling the RAII idiom. But the devil is in the details. And replacing fundamental types with classes is always error prone. (Which we are supposed to be preventing.) – jmucchiello Jul 08 '13 at 21:23
  • @jmucchiello: Using a simple class is infinitely easier than remembering to add `=nullptr` to all the corner cases that using a raw pointer creates. Just because the questioners toolchain supports `=nullptr` doesn't mean that it is the right answer. The class in the question fulfills the RAII idiom. Replacing fundamental types with classes is very common, to prevent the same error the OP is trying to avoid. See . – Mooing Duck Aug 29 '13 at 17:27
-2

When you use a raw pointer and expect the value 0 to express that the pointer is not yet initialized, the value 0 is in a sense a "sentinel value", or a "magic cookie." Think of a magic cookie as a special value that indicates state, but is otherwise indistinguishable from normal, valid values for that datatype.

Magic cookies being Bad for all the usual reasons might be triggering your sense that this is brittle and ultimately not needed. A solution that doesn't use a sentinel value might feel better.

One such solution is to use boost::optional:

typedef Foo* FooPtr;
boost::optional <FooPtr> foo;
// ...
if (!foo)
{
  // the pointer is not yet initialized
} 
else
{
  // the pointer IS initialized
  Foo* theFoo = *foo;
}

Initializing the pointer is simple:

foo = new Foo;

optional is not a sentinel value because you do not store a special value of Foo* to indicate that the pointer isn't initialized. The only job that optional does is to say "yes" or "no" in answer to the question "is the pointer initialized?"

John Dibling
  • 99,718
  • 31
  • 186
  • 324