C++ and the choice between references and pointers

09/30/20 • Carsten Haubold

Today at work I refactored a chunk of code and came across a variety of wrong usages of ownership/existence of pointers and references.

References cannot be nullptr

Can you tell me what's wrong here?

void myFunc(Logger* logger)
{
    logger->log("hello world");
}

Right... you're allowed to do that:

myFunc(nullptr);

... and BOOM segfault. Because nobody checked for nullptr. Or actually, the code was never meant to work without a Logger, so the choice of a pointer that could be nullptr is just wrong. It could have been so easy. Just use a reference instead of a pointer:

void myFunc(Logger& logger)
{
    logger.log("hello world");
}

Et voila, you'd have to try really hard to make this crash.

Use a pointer to express an object could be nullptr

Right in the same refactoring session, I came across an object that was only initialized under certain circumstances. But it was passed around via reference.

class MyObj {
public:
    void init() {
        // do stuff
        _initialized = true;
    }

    bool isInitialized() const { return _initialized; }

    void methodA() {
        if (!_initialized)
            throw std::runtime_error("not ready!");

        // do stuff
    }

    void methodB() {
        // NO CHECK FOR INITIALIZATION HERE ???
        // do stuff
    }
private:
    bool _initialized = false;
};

All this logic of initializing a MyObj, the error prone if(initialized)-precondition inside the methods... is not needed when you pass around a pointer that could be nullptr instead of an uninitialized reference...

More questions arise at the call site:

void doSomething(MyObj& obj) {
    // should we check for isInitialized, or will the methods of MyObj take care of that?
    if (!obj.isInitialized())
        throw std::runtime_error("argh");

    obj.methodA();
    obj.methodB();
}

MyObj a;
a.init(); //--> what would happen without initialization, crash, or undefined ???
doSomething(a);

The much simpler, and trivial to use option is this:

class MyBetterObj {
public:
    MyBetterObj() {
        // do any initialization here
    }

    void methodA() {
        // do stuff
    }

    void methodB() {
        // do stuff
    }
};

void doSomething(MyBetterObj* obj) {
    // check for nullptr here, as we accept a pointer argument
    if (obj == nullptr)
        return;

    obj->methodA();
    obj->methodB();
}

MyBetterObj a;
doSomething(&a);
doSomething(nullptr);

For more information on this, see the isocpp guidelines.

Happy coding!