The better language tries to break free

The Eradication of the Raw Pointer in C++11

The necessity for using raw pointers in C++ has decreased continuously. In this post I list replacement constructs for raw pointers and discuss a concrete example of problematic pointers in interfaces.

Planned Obsolescence of the Pointer

std::shared_ptr and std::unique_ptr are the foundation on which all things living on the heap are build (at least since C++11). And it is a stable foundation. Rarely, there is a good reason for not using a smart pointer to manage an allocated object. For instance, std::unique_ptr comes with close to zero overhead.

One may come to the conclusion that the committee wants to hold off users from having to use pointers in any form. We have an alternative to raw pointers for almost all of their classical use cases:

The Heap

Obviously, pointers are needed to hold objects on the heap. If you want runtime polymorphism, there is no way around it. But we have smart pointers. These clearly express the ownership.

Variable-Length Arrays

If you need variable-length arrays, then you may be tempted to invoke new[]. But wait, std::vector is perhaps the most widely used container in C++. It is straight-forward to use and superb in terms of efficiency.

Containers And Polymorphism

Okay, but how about runtime polymorphism plus containers? That’s where we need a std::vector<T*>, correct? The smarter alternative is boost’s pointer containers. Although, I have never grown a huge fan of them. But once more: pointers are not necessary here! Obviously, we have std::vector<std::unique_ptr<T>> and if you want the same thing with references, there is std::reference_wrapper that works as expected with standard containers.

Exhibit A: Usage of std::reference_wrapper in a STL container.

#include <vector>
#include <functional>

struct Base { virtual ~Base(){} };
struct Derived1 : public Base { virtual ~Derived1(){} };
struct Derived2 : public Base { virtual ~Derived2(){} };

int                    main(
    int   argc,
    char* argv[])
{
    Derived1                                   d1;
    Derived2                                   d2;

    std::vector<std::reference_wrapper<Base> > refVec;

    refVec.push_back(d1);
    refVec.push_back(d2);

    return 0;
}

Optional References

Finally, we can use a raw pointer to represent either a reference to a value (obtained by dereferencing the pointer) or the absence of value (i.e., NULL or std::nullptr in which case dereferencing the pointer will remind of you Tony Hoare’s apology for his billion-dollar mistake).

A reference or nothing, sounds familiar? I pointed out previously the similarities between pointers and std::optional<T>. But std::optional<T> actually means ‘value’ or ‘nothing’, while we want ‘reference’ or ‘nothing’. This could be represented by an optional reference (i.e., std::optional<T&>). While optional references did not make it into C++14, std::optional<std::reference_wrapper<T>> seems to be an option (pun intended).

Raw Pointers Be Gone

As C++ strives to become more novice friendly, I am inclined to say with respect to raw pointers, the less the better. In controlled environments, arcane pointer arithmetic can still be used to achieve top performance. I am thinking of a heavy-duty class that operates on pointers in its implementation.

Raw pointers are powerful, but dangerous. The downside of being powerful (or here expressive) is ambiguity. As shown in the use cases above, pointers can mean a lot of things and therefore when you see a pointer in an API, the meaning of the raw pointer is not expressed clearly.

For instance, one item of the Bloomberg coding standard I am not very fond of is the idea that return values of functions are passed as pointers to the function (i.e., we distinguish between input and output parameters of a function). The actual return value often is an int which indicates success or yields an error code. For example:

class Object{};
auto   myFunction(const int&,Object*)->int;
Object result;
int    inputArg = 0;
int    rc = myFunction(inputArg, &result);

This is old-school C-style and does not have much to do with modern C++ style, but prior to C++11 it made some sense.

Pro:

  • Convenience: Prior to C++11, we did not have std::tuple and std::tie. Emulating these two facilities using custom classes is much more effort.
  • Allocation: By passing a pointer to a function the responsibility to allocate the result remains in the client code and is not dictated to the client code by the function.

Con:

  • Ambiguity: When the result is not optional, but mandatory, why don’t we pass a reference?
  • Uncertainty: Handing out pointers to foreign code in general is worrisome. Will it take ownership of it? Will it attempt pointer arithmetic?
  • Interface Clarity: What about this other pointer argument which is supposed to be an input array?
  • Allocation: The allocation pro argument is actually a pretty strong one with respect to performance. But how about classes that are part of the function’s library and that are not supposed to live on the stack? It would be better if the library function forces correct usage of the result value upon us.

Conclusion

We have alternative to raw pointers, let’s use them. By using the alternatives we always clearly express our intent and reduce the chances of accidental bugs.

If we want to avoid raw pointers, we have to avoid direct allocation. This brings us to a recommendation by Herb Sutter:

Avoid using plain new or other raw unmanaged allocation directly.

And it brings us to the original motivation for this blog post which is discussed in the follow-up article: two strikes against std::make_unique and std::make_shared.

Post a Comment

All comments are held for moderation; Markdown formatting accepted;
By posting you agree to the following privacy policy.

This is a honeypot form. Do not use this form unless you want to get your IP address blacklisted. Use the second form below for comments.
Name: (required)
E-mail: (required, not published)
Website: (optional)
Name: (required)
E-mail: (required, not published)
Website: (optional)