5

In both C++11 and boost, smart pointers can be nullptr. I wonder why. That means that smart pointers must be checked for being nullptr every time they are passed to interface method from uncontrolled client code. Obviously, such check is performed in run time.

What if there would be smart pointers that can be created only via make_shared or make_unique and cannot be reset or reassigned to nullptr or raw pointer? This approach allows to ensure that pointer is not nullptr in compile time.

For example, in Java we always must check if object is not null (bad). But in Swift, we can explicitly make sure that argument (or variable) is not null in compile time (good).

UPD: Well, thank you much for answers and comments. I got idea. But is there any popular libraries that supports non-nullity compile time guarantee alongside ownership, maybe smart pointer wrappers?

George Sovetov
  • 4,942
  • 5
  • 36
  • 57
  • 4
    Well, the essence of smart pointers are their memory-owning semantics, not compile-time null checking (it isn't illegal for a smart pointer to not own memory at some point). You're probably looking for an `optional` encapsulator. – E_net4 Mar 06 '15 at 11:25
  • 2
    I suppose what you're looking for is a `smart_ref`. Which should probably exist along side `smart_ptr`. It's good practice to avoid unnecessary nulls but they can't be easily excluded in all cases. There are many cases where a default constructed object simply isn't a useful stand-in for 'no value here (yet/anymore/right now)'. `nullptr` is over used but don't jump through too many hoops to avoid using it either. – Persixty Mar 06 '15 at 11:53
  • 6
    _"In both C++11 and boost, smart pointers can be nullptr. I wonder why."_ Erm, because it's useful. Just because you can think of a case where a different behaviour would be useful sometimes doesn't mean there aren't other use cases. – Jonathan Wakely Mar 06 '15 at 11:56
  • @DanAllen of course, default constructor should be deleted from such class. – George Sovetov Mar 06 '15 at 11:56
  • @JonathanWakely probably, my words were quite reckless. I wonder why there is no possibility to avoid nulls when it is needed. – George Sovetov Mar 06 '15 at 11:59
  • There is a possibility, you just need to use a different smart pointer. `boost::shared_ptr` and `std::shared_ptr` are general purpose tools for the most common uses, and it's more common to want to be able to have an empty pointer that owns nothing. If you want something different then use a different tool. – Jonathan Wakely Mar 06 '15 at 12:02
  • 2
    @George which then bars things like arrays of them. I've worked with standard libraries that don't accept `NULL` (in Objective-C) it was more trouble than its worth. There was a clear decision in the Java libraries to let them back in. As I said, they're over used but that isn't a sufficient argument to rule them out of a very general purpose library. – Persixty Mar 06 '15 at 12:20
  • Note that there are no reasonable semantics for moving a smart pointer with a non-null guarantee when all you have is non-destructive move semantics (as is the case in C++). Which means that a non-null shared_ptr is inefficient, and a non-null unique_ptr outright impossible (it decays to being a scoped_ptr). – Sebastian Redl Mar 06 '15 at 15:59
  • 1
    Just to throw this out there, but are you sure you need a pointer at all? Perhaps what you need is a plain reference? – Andre Kostur Mar 06 '15 at 16:37
  • @DanAllen that's another topic to discuss and argue. – George Sovetov Mar 06 '15 at 21:46
  • @SebastianRedl I still see reasoning. Probably, I misunderstood the concept. – George Sovetov Mar 06 '15 at 21:49
  • @AndreKostur I discussed that at work today. Yes, if we don't need to transfer or share ownership, reference is appropriate. – George Sovetov Mar 06 '15 at 21:50

2 Answers2

6

std smart pointers exist for one reason—to implement the concept of ownership. Their responsibility is to clearly define who owns the pointee (i.e. who and how ensures its safe destruction).

Large parts of std are really composed of low-level basic building blocks. While they can be used straight away in client code, they are not supposed to be an all-encompassing solution. They give you single-purpose tools which you cna mix & match to create something you need.

The std smart pointers are eactly "raw pointers + ownership." Raw pointers can be null and can be reseated, so std smart pointers can as well. Nothing prevents you from creating your own "std smart pointer + non-nullity" class(es) and using them in your code.

On the other hand, there are very valid use cases for a null smart pointer. If std smart pointers enforce non-nullity, and you needed null-supporting smart pointers, you'd have a much harder time implementing that. It's easier to add a validity constraint than to remove it when you can only do it by adding to the original class.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
4

For std::unique_ptr is impossible to require no null, consider this:

std::unique_ptr<int> p = std::make_unique<int>();
std::unique_ptr<int> q = std::move(p);

What value will have p and q? If we ban null option this become impossible to implement. Even if we consider destroying move, we will have even worse situation. This is because you will be not allowed to test p, any use will be UB.

For std::share_ptr It could be possible to require it but this will heavy hinder any other use that could used nullable pointers. Standard library is too generic to allow that limitation.

Overall idea of having compile time guarantee of existing object pointed by pointer is very valuable but you try used wrong tool for this. Usually this is done by using & not pointers.

To solve your needs I suggest creating warper around std::share_ptr:

template<typename T>
class always_ptr
{
    std::shared_ptr<T> _ptr;
public:
    always_ptr() = delete; //no default constructor
    always_ptr(const always_ptr& a) : _ptr{ a._ptr } {  }
    explicit always_ptr(T* p)
    {
        if (!p) throw std::Exception(); //only way to guarantee this is not null
        _ptr = std::shared_ptr<T>(p);
    }
    T* get() { return _ptr.get(); }
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr.get(); }
    explicit operator bool() const { return true; } //always true
};
Yankes
  • 1,958
  • 19
  • 20
  • I always thought it leads to undefined behaviour when variable is used after move. I see no problem here. – George Sovetov Mar 06 '15 at 21:39
  • As for implementation, it's pretty clear for me how to do that. I propose that we need something like `make_always` function template. – George Sovetov Mar 06 '15 at 21:42
  • @1 No, after move object is in "random" state, it can be empty, still have that value or have new value that we don't see before (this can happens if someone use `std::swap` to do move). @2 Yes, this was very very basic example. – Yankes Mar 06 '15 at 23:08
  • but there is no sense to use object being in random, undefined state, right? – George Sovetov Mar 07 '15 at 06:46
  • @George You can reuse it for different purpose. All class invariants still hold. This rule was created to allow all implementation that steal resources without breaking class. After this discussion I get conclusion that is possible to create `unique_ptr` that is not null, but this require creating new object for every new pointer and use `swap` to move. But that again destroy all good properties of `unique_ptr`. – Yankes Mar 07 '15 at 11:10
  • I understand what you mean but we have slightly different opinions :) Thank you for your advice. – George Sovetov Mar 07 '15 at 12:29
  • I created a [`smart_ref`](https://stackoverflow.com/a/73753698/3779655) which might solve the problem for most use-cases. One thing to note is that `std::move()` leaves the moved object in a state showing undefined behavior when accessed afterwards. – darkdragon Sep 17 '22 at 10:24