5

While working with std::shared_ptr a lot I kind of miss a shared_ref implementation. That is a specialization of shared_ptr, which guarantees, that it never wraps a nullptr (given right usage, of course). I kind of wonder why it is not in the C++11 standard. Are there any mayor problems when implementing it? On the top of my head I cannot think of any.

EDIT:

I would expect to have an interface similar to:

template <typename T>
class shared_ref {
public:
  shared_ref( T&& ref );
  T& get();
  T* operator&() const;

  template< class Y > 
  void reset( Y&& obj );

  long use_count() const;
  bool unique() const;

  void swap( shared_ref& r );
};
abergmeier
  • 13,224
  • 13
  • 64
  • 120
  • 1
    `shared_ptr` shouldn't be `nullptr` given right usage, either. It's not there probably because nobody thought about it, or they did and rejected it on the grounds of not being useful enough to justify adding it. – Cat Plus Plus Jul 06 '12 at 15:28
  • 3
    `shared_ptr` will be `nullptr` e.g. when using the default constructor. – abergmeier Jul 06 '12 at 15:40
  • 1
    `shared_ref` wouldn't have a default constructor, so just don't use default ctor on `shared_ptr`. – Cat Plus Plus Jul 06 '12 at 15:59
  • 3
    That is not the point. The point is, that if a function gets passed a `shared_ptr` there is no guarantee, that it is not null. If you really want to write correct code you would have to check for nullptr before using the ptr. Given, that 95% of all coders don't give a damn. – abergmeier Jul 06 '12 at 16:18
  • 1
    @LCIDFire You can't make correct code out of incorrect code. – R. Martinho Fernandes Jul 06 '12 at 16:23
  • 1
    @R.MartinhoFernandes Please elaborate, I don't get what you are trying to say. – abergmeier Jul 06 '12 at 16:24
  • 1
    @LCIDFire If the function is not supposed to work with null pointers, passing a null pointer to it is a bug. Trying to fix that bug from *within* the function is not going to work, because the buggy code is outside. – R. Martinho Fernandes Jul 06 '12 at 16:29
  • 5
    @R.MartinhoFernandes, the question is how best to enforce a contract, the contract being that the pointer is not null. The earlier you catch such errors the better. The gold standard is strict type checking, where you catch the error at compile time. – Mark Ransom Jul 06 '12 at 16:36
  • @Mark sure, but you don't catch the error at compile time by checking if your arguments are nullptr. That's what I'm saying: If you want to write correct code you don't have to check for nullptr before using the pointer, you have to make it not be null in the first place. – R. Martinho Fernandes Jul 06 '12 at 16:42
  • 5
    @R.MartinhoFernandes That is exactly the reason why I want a `shared_ref`! – abergmeier Jul 06 '12 at 16:43

2 Answers2

3

Are there any mayor problems when implementing it?

Here's one: you can't take ownership of a reference. The whole point of a smart pointer is to claim ownership of the pointer itself. shared_ref can't work because you can't control the lifetime of a reference.

And no, this isn't going to fly either:

shared_ref( T&& ref ) : p(&ref) {}

The user may have given you a stack variable, which now means you have "shared" ownership between this object and a stack variable. And stack variables cannot share ownership with something.

You can only control the lifetime of a pointer. And pointers can be NULL. Therefore, the only thing you can do is a runtime check to see if a pointer is NULL.

The absolute best you can do is an interface equivalent to shared_ptr except that it has no default constructor and throws in the event of being given NULL. Is that really worth creating a whole new pointer type over?


The C++ Core Guidelines support library has the not_null template, which can be applied to most pointer-like types. So you can use not_null<shared_ptr> when you want to verify that a pointer isn't NULL, but only once when it enters use. After the initial creating of the pointer, it doesn't need to check again.

Granted, you can't force other people to use them, but use of the type consistently will resolve the issue.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Well you are right. I would perhaps solve this by requiring `T` to have a move constructor. Then you could internally build a pointer and indeed take ownership. This kind of verbose requirement is probably the reason, why it is not in the standard library. – abergmeier Jul 06 '12 at 17:31
  • 2
    Also perhaps the name is wrong - perhaps `shared_obj` is probably the more correct name!? – abergmeier Jul 06 '12 at 17:33
  • There could still be value in having language support for non-nullable pointers (technically all pointers can be null, but compile-time checks on such a meta-type would still be possible in exactly the same way in which Swift does it). The only two checks that'd need to be performed are: 1. Make sure there's no direct `nullptr` assignment, 2. Make sure optional pointers are unwrapped before trying to assign them to a non-optional pointers. (All this could be verified at compile-time). PS: one could still mess with the memory, but that's on you. – diegoreymendez Jun 05 '16 at 04:34
  • @diegoreymendez: The Core C++ Guidelines support library provides the `not_null` template as a library solution. The only defect it has is that it cannot work with `unique_ptr` or another non-copyable type. And whether a pointer is null or not is a runtime property, not a compile-time one. To turn it into the latter makes certain entirely valid constructs impossible to write. – Nicol Bolas Jun 05 '16 at 04:40
  • In the scenario I proposed regular nullable pointers would still exist and work in the very same way they do now (how would a new and optional feature make valid constructs impossible to write?). And yes, currently pointers have to be checked for null at runtime (status quo). My whole suggestion is being able to declare non-nullable pointers that can't be directly assigned `nullptr` or nullable pointers (unless you first "unwrap" them). – diegoreymendez Jun 05 '16 at 05:05
  • See the answer explaining what "unwrapping" is here - this would be perfectly feasibly in C++ too: http://stackoverflow.com/questions/24034483/what-is-an-unwrapped-value-in-swift – diegoreymendez Jun 05 '16 at 05:07
  • @diegoreymendez: Since `not_null` exists, what reason exactly would there be to make this a *language* feature? – Nicol Bolas Jun 05 '16 at 05:08
  • If `not_null` does all I mentioned, then none - it'd be enough. But I can't comment on it since it seems there's no OSX implementation yet? Time will tell. – diegoreymendez Jun 05 '16 at 05:30
  • 1
    Re. "the absolute best" paragraph, it would also have to have different behaviour in the case of an explicit call to `reset(nullptr)`. And I don't really agree with the previous paragraph: a `shared_ptr` manages lifetime of *an object*, not of a pointer. It might use a pointer as part of its internal representation but that's not pertinent. Conceptually there is no problem with a container managing lifetime of a shared object without ever allowing there to be no current object being managed; but that would simply be a different class to `shared_ptr`. – M.M Jan 31 '17 at 10:40
  • "The user may have given you a stack variable, which now means you have "shared" ownership between this object and a stack variable." Well, the user may have given your shared_ptr the address of a stack variable, which now means you have "shared" ownership between your shared_ptr and a stack variable. shared_ptr can't prevent the user from doing this mistake, so that shared_ref can't either is not really speaking against having a shared_ref class. – Kaiserludi Sep 12 '18 at 17:52
  • @Kaiserludi: It's a lot easier to write `shared_ref(stack_variable)` than it is to write `shared_ptr(&stack_variable)`. The latter requires the user to write that `&`, which means they had to realize that they need to pass a pointer. Which makes them reconsider whether they really want to do this. That's not the case for `shared_ref`; it can just take a variable directly. – Nicol Bolas Sep 12 '18 at 17:56
  • But aren't we talking about `shared_obj(std::move(stack_variable))` instead of `shared_ref(stack_variable)`? –  Mar 10 '20 at 08:41
0

There are only two ways for a shared_ptr to be null - either it was default constructed, or it was assigned a null value at some point. Since you already agree it doesn't make sense to default construct your hypothetical shared_ref class, that leaves only the second condition.

If you tried to assign a nullptr to your shared_ref object, what would you expect to happen? Should it throw an error? It's trivial to do the same thing with a regular shared_ptr using a simple template function:

template<typename T>
T* notnull(T* ptr)
{
    if (ptr == std::nullptr)
        throw std::invalid_argument(std::string("nullptr"));
    return ptr;
}

std::shared_ptr<int> pint = notnull(GetIntPtr());

Generally things aren't added to the standard unless there's a compelling need with no easy workarounds.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 1
    See my edit. I would perhaps also allow overloads for pointers, but they would have to throw exceptions when finding a ptr to be null. – abergmeier Jul 06 '12 at 17:16
  • @LCIDFire, again I ask: what is supposed to happen when you try to assign a nullptr to this object? I.e. `T* p = nullptr; my_ref.reset(*p);` – Mark Ransom Jul 06 '12 at 17:21
  • The only real problem is that getting the ref _easier_ is a little problematic. Calling `get()` is indeed a little verbose :( – abergmeier Jul 06 '12 at 17:21
  • That is, like anywhere in the std library, undefined behavior. And a bug in the calling code, btw. – abergmeier Jul 06 '12 at 17:23