Avoiding null pointers and preserving polymorphism

In my code, I just noticed that I quite often need to check the nullptr value, although nullptr may not be possible (in accordance with the specified requirements).

However, nullptr can still happen, as other people can send nullptr, believing that this is normal (unfortunately, not every read / write specification), and this defect cannot be detected if the problem does not start at run time during testing time (and high test coverage is expensive). Thus, this can lead to numerous post-release errors reported by customers.

eg.

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 just used value types and passed by reference and copy. However, in some cases, I need a polymorphism that requires pointers or references.

So, I started using 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 it is practical? Or am I missing something? Are there any potential problems with this practice?

EDIT:

"Problem" using links - you cannot move the ownership of the link (for example, return an object).

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

The problem with rvalue links (does polymorphism work with rvalue links?), Then it becomes that you cannot share property.

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

A "safe" pointer based on shared_ptr sounds like a good idea, but I would still like to find a solution in which non-void will be enforced at compile time, maybe this is not possible.

+6
c ++ polymorphism
source share
4 answers

I personally prefer to encode null capability in a type and thus use boost::optional .

  • Create a data_holder class that always has data (but allows polymorphism)
  • Define your interfaces in terms of data_holder (nonzero) or boost::optional<data_holdder>

Thus, it is very clear whether it can be zero.

Now the tough part is to ensure that data_holder never holds a null pointer. If you define it using the constructor of the form data_holder(data*) , the constructor can throw.

On the other hand, it can simply accept some arguments and defer the actual construct to Factory (using the Virtual Designer Idiom). You still check the Factory result (and drop if necessary), but you only have one place to check (factory), and not every build point.

You might also want to check boost::make_shared to see the forwarding of the argument in action. If you have C ++ 0x, you can efficiently redirect the arguments and get:

 template <typename Derived> data_holder(): impl(new Derived()) {} // Other constructors for forwarding 

Remember to declare the default constructor (not a template) as private (and not define it) in order to avoid an accidental call.

+3
source share

You can always use a null object template and only links. You cannot pass (well, you can, but this is a user error) a null link.

+4
source share

A non-null pointer is a well-known concept and is used, for example, in safe subsets of C. Yes, it can be beneficial.

And you should use a smart pointer for this. Depending on your use case, you can start with something similar to boost::shared_ptr or tr1::unique_ptr . But instead of just assert to specify a nonzero value on operator* , operator-> and get() , throw an exception.

ETA: Forget about it. Although I find this general approach useful (undefined behavior, etc. Etc.), It does not provide you with compile time checks for the non-zero value that you probably want. To do this, you would use such non-zero pointers throughout your entire code, and without language support this would still be impassable.

+2
source share

Usually, when you use virtual functions, as you described above, this is because you do not want to know about all the classes that implement the required interface. In your code example, you list all classes that implement the (conceptual) interface. This gets frustrating when you need to add or remove implementations over time.

Your approach also interferes with dependency management, since your class data depends on all classes, while using polymorphism makes it easier to limit dependency on specific classes. Using the pimpl idiom mitigates this, but I have always considered the pimpl idiom as annoying (since you have two classes that need to stay in sync to present one concept).

Using links or proven smart pointers seems like a simpler and more intuitive solution. Other people are already commenting on them, so I will not stop now.

+1
source share

All Articles