Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding null pointers and keeping polymorphism

In my code I just noticed that I quite often need to check for nullptr, even though nullptr should not be possible (according to specified requirements).

However, nullptr might still occur since other people might send a nullptr believing this is ok (unfortunately not everyone reads/writes specification), and this defect cannot be caught unless the problem is triggered in run-time during testing (and high test coverage is expensive). Thus it might lead to a lot of post-release bugs reported by customers.

e.g.

class data
{
     virtual void foo() = 0;
};

class data_a : public data
{
public:
     virtual  void foo(){}
};

class data_b : public data
{
public:
     virtual void foo(){}
};

void foo(const std::shared_ptr<data>& data)
{
    if(data == nullptr) // good idea to check before use, performance and forgetting check might be a problem?
        return;
    data->foo();
}

Usually I would simply use value-types and pass by reference and copy. However, in some cases I need polymorphism which requires pointers or references.

So I have started to use the following "compile time polymorphism".

class data_a
{
public:
     void foo(){}
private:
     struct implementation;
     std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy
};

class data_b
{
public:
     void foo(){}
private:
     struct implementation;
     std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy
};

class data
{
public:
     data(const data_a& x) : data_(x){} // implicit conversion
     data(const data_b& x) : data_(x){} // implicit conversion
     void foo()
     {
          boost::apply(foo_visitor(), data_);
     }
private:
     struct foo_visitor : public boost::static_visitor<void>
     {
          template<typename T>
          void operator()(T& x){ x.foo(); }       
     };

     boost::variant<data_a, data_b> data_;
}

void foo(const data& data)
{
   data.foo();
}

Does anyone else think this is a good idea, when practical? Or am I missing something? Are there any potential problems with this practice?

EDIT:

The "problem" with using references is you cannot move ownership of a reference (e.g. returning an object).

data& create_data() { data_a temp; return temp; } // ouch... cannot return temp;

The problem with rvalue references (polymorphism does work with rvalue references?) then becomes that you cannot share ownership.

data&& create_data() { return std::move(my_data_); } // bye bye data        

A "safe" pointer based on shared_ptr does sound like a good idea, but I would still like a solution where the non-nullness is enforced at compile time, maybe not possible.

like image 353
ronag Avatar asked Dec 11 '10 11:12

ronag


1 Answers

You can always use the null object pattern and references only. You can't pass (well you sort of can but that is the user's error) a null reference.

like image 61
Goz Avatar answered Oct 10 '22 19:10

Goz