Non-nullable smart pointers

1,855 views
Skip to first unread message

Pavel Kretov

unread,
Mar 8, 2015, 3:24:45 AM3/8/15
to std-pr...@isocpp.org
Hello everybody.

The problem I want you to discuss may have been already reviewed here,
but I did not found anything similar in the mailing list archive. I was
trying to explain the idea as well as I could, but I'm not a native
speaker, so I might fail at some points. Excuse me for that.

Have an nice weekend,
——— Pavel Kretov.


=== The problem ===

This is a common practice in programming to check input parameters for
functions. As many parameters are pointers, smart or regular, checking
them against "nullptr" is a *very* frequent operation, but what should
you do if pointer is null? There are many options:

— silently return from the function?
— print warning message and return?
— throw an exception?
— call abort() from <cstdlib>?
— use some kind of ASSERT() and hope it will do the right thing?
— skip the check altogether and get a segfault in case?

The exact choice may be different for different projects but each has
its own drawbacks.

— If you chose to silently return, can you be sure that this is safe
for the caller to stay unaware? Missing required parameter is a
programmer's mistake and they should be told of it as soon as
possible.

— If you chose to throw an exception, which one would you throw? Unlike
languages like Java, C# or Python, C++ has a relatively poor set of
predefined exceptions. It is also considered to be a good practice
in C++ to trow and exception only in case of really exceptional
situations and a single null pointer does not seem to be that much
serious matter. Moreover, when choosing exceptions you will have to
either pollute your functions' throw lists with std::invalid_argument
or just omit them entirely. The latter means it is easy for your code
user to forget to catch exceptions at all.

— Calling abort(), directly or as result of ASSERT, is no better then
silently ignoring the error. I'm pretty sure nobody wants their
enterprise level system to shut down without even syncing disk cache
and database transactions due to forgotten null pointer passed to
logger function.

— Ditto for not checking arguments at all.


=== The proposed solution ===

The problem could be completely eliminated in many cases if pointers
were not allowed to carry invalid values at all. These days C++ has a
strong movement toward smart pointers, leaving plain C pointers as a
legacy option or for very low-level things. Classes std::shared_ptr and
std::weak_ptr are commonly seen as a replacement.

Shared pointers are designed to mimic the behavior of plain pointers,
they can wrap any pointer, even the one which is not safe to deallocate
using "delete" operator. It offers its user a great number of ways to
misuse it.

I propose to create a subclass of shared pointers, which is somewhat
guaranteed to hold a valid object which can be safely deallocated. This
class should:

— be unable to hold nullptr;
— have no conversion constructors from plain pointers;
— have object allocation to be the only way for pointer to be obtained.

Lets call this class an std::object_ptr, after the term "object" which
is used throughout C++ standard text, and declare it like the following:

template <typename T>
class object_ptr: public std::shared_ptr<T> {
+ only copy and move constructors;
+ all operators applicable to std::shared_ptr<T>;
}

template <typename T, typename TAlloc, typename... TArgs>
object_ptr<T> allocate_object(const TAlloc& a, TArgs&&... args);

template <typename T, typename... TArgs>
object_ptr<T> make_object(TArgs&&... args);

The instance of the class must also be convertible to a shared pointer
which is never empty or null.

After the class is introduced, you may simply write your functions
arguments as std::object_ptr<T> if you expect them to be pointers to
valid objects only and just skip any further null-pointer validation, or
leave them as std::shared_ptr<T> in cases there nullability makes sense.
Or better use std::object_ptr together with std::optional, like the
following:

void Scene::paintSprite(const object_ptr<Sprite>&);

std::optional<std::shared_ptr<Sprite>>
Scene::findSprite(const Point&);

In the example above it is clear from the very declaration that painting
null sprite is not allowed (as being pointless) while function
"findSpline" can return sprite if it is able to find one, or can return
nothing otherwise.

We should also provide a way to create std::object_ptr from
std::shared_ptr or even plain pointers for legacy purposes, but there
should be the only one point there such a conversion is allowed. I'm not
quite sure about the exact name and signature, but it should be a single
function like that:

template <typename T>
std::object_ptr<T> object_ptr_cast(const std::shared_ptr<T>&)
throw(std::invalid_argument);

=== Conclusion ===

Eliminating null pointers validation may seem to be not that big deal to
create a special pointer type for that, but indeed it is a great
problem: thousands of people around the world, writing programs in
various languages, every day performs a lot of useless checks against
NULL, just to throw an exception which in most cases will never be
caught. We must do something to left those legacy checks behind and thus
improve code readability and "self-documentability".

To improve the situation, Java has introduced @NonNull arguments
attribute years ago (which is anyway broken), Scala uses the Optional[T]
pattern (as many other functional languages). There are languages like
Kotlin, which sees "T" as non-nulable reference by default and require
user to explicitly declare nullable references like "T?". (Moreover,
Kotlin can check arguments during compilation, with respect to code path
branching.) I think this approach will be widely adopted in the future
programming languages and C++ must take some steps in this direction.

Magnus Fromreide

unread,
Mar 8, 2015, 4:25:32 AM3/8/15
to std-pr...@isocpp.org
When I read your message I asked myself why you couldn't just take the
arguments by reference?

Could you please explain that as well.

/MF

Pavel Kretov

unread,
Mar 8, 2015, 4:47:37 AM3/8/15
to std-pr...@isocpp.org, Magnus Fromreide
> When I read your message I asked myself why you couldn't just take the
> arguments by reference?
> Could you please explain that as well.

Because there is a lot of situations there you cannot pass or return by
reference. Consider the examples I've given:

— you cannot pass argument to "addSprite" by reference because the
sprite is supposed to be stored and controlled by the Scene once
after it has been added;

— you cannot return a reference from "findSpline" function as the
spline may be not found;

Moreover, references are very easy to misuse too: you can accidentally
return a reference to stack-allocated variable or create object on heap
and return it the caller which may (and will!) forget to delete the
allocated memory.

Of course, references remain still useful in many cases (an should be
used then appropriate), but not in the ones when you want to get rid of
object-ownership problems using reference-counting with smart pointers.

And one more note: reference can be invalid too if you employ some
trickery like null-pointer dereferencing (which can also be done
accidentally).

——— K. P.

Nicol Bolas

unread,
Mar 8, 2015, 12:32:10 PM3/8/15
to std-pr...@isocpp.org
On Sunday, March 8, 2015 at 3:24:45 AM UTC-4, Pavel Kretov wrote:
Hello everybody.

The problem I want you to discuss may have been already reviewed here,
but I did not found anything similar in the mailing list archive. I was
trying to explain the idea as well as I could, but I'm not a native
speaker, so I might fail at some points. Excuse me for that.

Have an nice weekend,
——— Pavel Kretov.


=== The problem ===

This is a common practice in programming to check input parameters for
functions. As many parameters are pointers, smart or regular, checking
them against "nullptr" is a *very* frequent operation, but what should
you do if pointer is null? There are many options:

  — silently return from the function?
  — print warning message and return?
  — throw an exception?
  — call abort() from <cstdlib>?
  — use some kind of ASSERT() and hope it will do the right thing?
  — skip the check altogether and get a segfault in case?

If a function takes a pointer type, and it cannot work if it is given a NULL pointer, then what has happened here is a violation of the implicit contract between the function and whomever called it. And a violation of this contract represents a program error, which means that the program is no longer in a reasonable state.

Therefore, the program needs to halt execution.
 
The exact choice may be different for different projects but each has
its own drawbacks.

  — If you chose to silently return, can you be sure that this is safe
    for the caller to stay unaware? Missing required parameter is a
    programmer's mistake and they should be told of it as soon as
    possible.

  — If you chose to throw an exception, which one would you throw? Unlike
    languages like Java, C# or Python, C++ has a relatively poor set of
    predefined exceptions. It is also considered to be a good practice
    in C++ to trow and exception only in case of really exceptional
    situations and a single null pointer does not seem to be that much
    serious matter. Moreover, when choosing exceptions you will have to
    either pollute your functions' throw lists with std::invalid_argument
    or just omit them entirely. The latter means it is easy for your code
    user to forget to catch exceptions at all.

If the user doesn't catch an exception, the application halts. If the application can't halt, then that's the problem of the user. That being said, I wouldn't throw an exception just for a NULL pointer. A pointer being not-NULL is a precondition, and a violation of a precondition should not result in an exception.

It should result (in debug) in program termination.

Also, "functions' throw lists" are no longer part of C++.
 

  — Calling abort(), directly or as result of ASSERT, is no better then
    silently ignoring the error. I'm pretty sure nobody wants their
    enterprise level system to shut down without even syncing disk cache
    and database transactions due to forgotten null pointer passed to
    logger function.

If you're talking about an application that isn't allowed to terminate, then that means that your contract with the outside world needs to be permissive. Which means you have to accept NULL and define what should happen when you're given it. So you need to figure out how to do something innocuous if you get a bad parameter.

Even so, most "enterprise applications" won't tolerate random pointers being NULL anyway. They check where they're supposed to: at the interface level. Once past the interface, everything is expected to be sane.
 
  — Ditto for not checking arguments at all.


=== The proposed solution ===

The problem could be completely eliminated in many cases if pointers
were not allowed to carry invalid values at all. These days C++ has a
strong movement toward smart pointers, leaving plain C pointers as a
legacy option or for very low-level things. Classes std::shared_ptr and
std::weak_ptr are commonly seen as a replacement.

Shared pointers are designed to mimic the behavior of plain pointers,
they can wrap any pointer, even the one which is not safe to deallocate
using "delete" operator. It offers its user a great number of ways to
misuse it.

I propose to create a subclass of shared pointers, which is somewhat
guaranteed to hold a valid object which can be safely deallocated. This
class should:

  — be unable to hold nullptr;
  — have no conversion constructors from plain pointers;
  — have object allocation to be the only way for pointer to be obtained.

And what if I already have a pointer, perhaps obtained via transfer of ownership from the outside world? Or if I need to use a special deleter on it?

Why not just have the constructor that takes a pointer check for NULL and fail if it sees it? It can be explicit (just like shared_ptr's pointer constructor), so it can't accidentally acquire ownership.
 

So... what does it mean to have an optional pointer that is committed (ie: has a value), but that value is NULL?

It makes no sense to use optional with an object that already has the concept of an empty state, unless there needs to be a distinction between "empty" and "not-applicable". So if you need to return an object or not an object... why would you not just return the shared_ptr?


We should also provide a way to create std::object_ptr from
std::shared_ptr or even plain pointers for legacy purposes, but there
should be the only one point there such a conversion is allowed. I'm not
quite sure about the exact name and signature, but it should be a single
function like that:

     template <typename T>
     std::object_ptr<T> object_ptr_cast(const std::shared_ptr<T>&)
                                       throw(std::invalid_argument);

=== Conclusion ===

Eliminating null pointers validation may seem to be not that big deal to
create a special pointer type for that, but indeed it is a great
problem: thousands of people around the world, writing programs in
various languages, every day performs a lot of useless checks against
NULL, just to throw an exception which in most cases will never be
caught.
 
I disagree with the idea that this is necessarily common (or at least, that it has to be). It is far more common to pass an object to a function without transferring ownership to it. In such cases, a reference is entirely appropriate.

Given the above, if you're passing a pointer, then it's usually something that is one of the following: 1) can't be a reference (for some reason, like a naked C array or whatever), 2) is conceptually nullable (and therefore, the function is expecting to take a possibly null pointer), or 3) is a transfer of ownership. The cases you're worried about are #1 and #3.

I don't think they happen often enough to require the creation of a whole new object type.

We must do something to left those legacy checks behind and thus
improve code readability and "self-documentability".

To improve the situation, Java has introduced @NonNull arguments
attribute years ago (which is anyway broken), Scala uses the Optional[T]
pattern (as many other functional languages). There are languages like
Kotlin, which sees "T" as non-nulable reference by default and require
user to explicitly declare nullable references like "T?". (Moreover,
Kotlin can check arguments during compilation, with respect to code path
branching.) I think this approach will be widely adopted in the future
programming languages and C++ must take some steps in this direction.

C++ already has non-nullable references; they're called references. Sure, you can force a reference to be NULL, but that generally requires deep, personal stupidity. And C++ has never prevented a programmer from falling victim to deep, personal stupidity.

Pavel Kretov

unread,
Mar 8, 2015, 3:51:49 PM3/8/15
to Nicol Bolas, std-pr...@isocpp.org
Thank you for your long, detailed response.
As a foreword to my answer I'd like to say that my proposal is not to
replace old good references with a custom template class, but to replace
std::shared_ptr in many cases with its non-nullable version. I feel
strong about making non-nullable pointer the default option with
nullable pointer as a special case. I don't consider regular pointers as
I believe that future programs will use them exceptionally rare.

> If a function takes a pointer type, and it cannot work if it is
> given a NULL pointer, then what has happened here is a violation of
> the implicit contract between the function and whomever called it.
> And a violation of this contract represents a program error, which
> means that the program is no longer in a reasonable state.

The point is to turn that implicit contract into an explicit one. Now it
can be frequently seen in functions' documentation comments:

/// @param ptr: Pointer to something, must not be null.

This is the implicit contract, it is nice and easy, until you want to
actually enforce it. Doing that leads to great deal of problems, or you
will end up with a halted program.

> Therefore, the program needs to halt execution.

In some cases you don't really want the entire program to halt, but, for
example, to unload the plugin, cleanup after it and reload, while
sending the programmer a happy letter about an accident. Never letting
such a situation happen due to just a missed null pointer seems to be a
better approach to me, as these days programmers have a lot of much more
serious problems then dealing with null pointers.

> If the user doesn't catch an exception, the application halts. If the
> application can't halt, then that's the problem of the user. That
> being said, I wouldn't throw an exception just for a NULL pointer. A
> pointer being not-NULL is a precondition, and a violation of a
> precondition should not result in an exception.
>
> It should result (in debug) in program termination.

And what would you do in release? "Live fast die young" is suitable not
for each program. Nobody is guaranteed against accidental invalid
pointer, the proposal is to eliminate the problem entirely.

> Also, "functions' throw lists" are no longer part of C++.

Okay, my fault. Thank to you for teaching me the difference between
"throw()" and "noexcept" — the former is deprecated :). But you you
still unable to mark function with "noexcept" keyword if you throw an
exception when checking nullable parameters.

> If you're talking about an application that isn't allowed to
> terminate, then that means that your contract with the outside world
> needs to be permissive. Which means you have to accept NULL and
> define what should happen when you're given it. So you need to
> figure out how to do something innocuous if you get a bad parameter.

That permissiveness makes code paths more complex and can lead to
undesired consequences. For example, you have got invalid pointer as a
constructor parameters, what would you do? You cannot cancel object
construction, but you don't want to construct invalid object either. So
you have to add more logic to you class to distinguish between properly
constructed objects and failback-constructed objects. Or throw an
exception which you generally dislike to do.

> Even so, most "enterprise applications" won't tolerate random
> pointers being NULL anyway. They check where they're supposed to: at
> the interface level. Once past the interface, everything is expected
> to be sane.

I understand that. Proposed pointers do the same thing: they are
guaranteed to be valid, no further checks needed at all neither on level
of the interface, nor below levels. Moreover, the interface is clearly
determined to accept only valid pointers, not nulls.

> And what if I already have a pointer, perhaps obtained via transfer
> of ownership from the outside world? Or if I need to use a special
> deleter on it?
>
> Why not just have the constructor that takes a pointer check for
> NULL and fail if it sees it? It can be explicit (just like
> shared_ptr's pointer constructor), so it can't accidentally acquire
> ownership.

We should definitely have the way to archive this, but I personally
would prefer to separate it from object_ptr class, just to make it
*extremely* clear for user that this is a point there ownership is
acquired or re-acquired. Given that, the object_ptr itself can stand
free from checks and exception-raising.

> We should also provide a way to create std::object_ptr from
> std::shared_ptr or even plain pointers for legacy purposes, but
> there should be the only one point there such a conversion is
> allowed. I'm not quite sure about the exact name and signature, but
> it should be a single function like that:
>
> template <typename T> std::object_ptr<T> object_ptr_cast(const
> std::shared_ptr<T>&) throw(std::invalid_argument);
>
> (Me, 2015-03-08 10:24:45 UTC+4)

This is the function proposed to get non-nullable pointers from the
nullable ones. I think it should be picked with more appropriate name
(it is not really a cast, as I feel), with more elaborated signature and
overloads. Overloaded version may include parameters to pass a custom
deleter and have other features from shared_ptr<T> constructors which
are absent from object_ptr.

> It makes no sense to use optional with an object that already has the
> concept of an empty state, unless there needs to be a distinction
> between "empty" and "not-applicable". So if you need to return an
> object or not an object... why would you not just return the
> shared_ptr?

Ops, I have a mistake in my example, sorry.

- std::optional<std::shared_ptr<Sprite>>
+ std::optional<std::object_ptr<Sprite>>
Scene::findSprite(const Point&);

Now it makes sense. Moreover, object_ptr can play really well with
std::optional. If object_ptr is ever going to become widely adopted,
optional+object_ptr would be better option then plain std::shared_ptr.

Compare:

std::optional<std::object_ptr<Sprite>>
Scene::findSprite(const Point&);

auto maybeSprite = scene.findSprite(point);
if (maybeSprite)
scene2.addSprite(maybeSprite.value());

with:

std::shared_ptr<Sprite>>
Scene::findSprite(const Point&);

auto sprite = scene.findSprite(point);
if (sprite)
scene2.addSprite(std::object_ptr_case(sprite));
^ Ugly part here.

The former case lets you completely avoid object_ptr to shared_ptr
conversions, without an additional object creation.

> I disagree with the idea that this is necessarily common (or at
> least, that it *has* to be). It is far more common to pass an object
> to a function without transferring ownership to it. In such cases, a
> reference is entirely appropriate.

I'm not about not using references there they should be.

> Given the above, if you're passing a pointer, then it's usually
> something that is one of the following: 1) can't be a reference (for
> some reason, like a naked C array or whatever), 2) is conceptually
> nullable (and therefore, the function is expecting to take a
> possibly null pointer), or 3) is a transfer of ownership. The cases
> you're worried about are #1 and #3.
>
> I don't think they happen often enough to require the creation of a
> whole new object type.

But they are often, at least the third one. If it wasn't, no smart
pointers would be introduced to the language at all.

> C++ already has non-nullable references; they're called
> *references*.

How do you call a reference with reference counting?

> Sure, you can *force* a reference to be NULL, but that generally
> requires deep, personal stupidity. And C++ has never prevented a
> programmer from falling victim to deep, personal stupidity.

Likewise, you are free to create an object_ptr instance holding null
pointer by using direct memory access (or famous "#define private
public" technique). Nobody can forbid you to shoot in your foot, but
that kind of shooting should be made as hard as possible in order to
safe others from the ricochet.

——— Pavel Kretov.

Nicol Bolas

unread,
Mar 8, 2015, 7:01:05 PM3/8/15
to std-pr...@isocpp.org, jmck...@gmail.com
I think "object_ptr" is very much the wrong name. Why? A big part of the problem is that the name obliterates the most important word in its predecessor: shared. What ownership semantics does "object_ptr" have: shared, unique, none, something else? What would you name the "unique_ptr" analog to "object_ptr", or the "weak_ptr" equivalent? You'll still need those to complete your interface (well, maybe not "weak_ptr"; that one you can probably reuse).

It seems to me that what you are proposing is something analogous to std::reference_wrapper: a handle to an object that undeniably exists. You asked in your post how you have a reference-counted reference. Well, isn't that exactly what you're proposing?

Pointers are how you refer to objects that might not be there. References are how you refer to objects that *must* exist. Even though language references are not rebindable after creation, std::reference_wrapper is. So it's very much the same concept as what you want, only without the ownership semantics.

In short, std::reference_wrapper is the std::observer_ptr of owning objects that have reference semantics (rather than pointer semantics).

So I think what you're proposing would be better called "shared_ref". You'll also need "unique_ref", possibly "weak_ref", and certainly "observer_ref" as a typedef of "reference_wrapper" to complete the set.

Oh, you should still have explicit conversions from the equivalent _ptr types (obviously the "unique_" ones will require moves from their alternate types). And certainly, shared_ref should share memory with shared_ptr and vice-versa. They both ought to use the same back-end.

The only difference is that, if you hand them a NULL pointer, they'll throw something.


On Sunday, March 8, 2015 at 3:51:49 PM UTC-4, Pavel Kretov wrote:
Thank you for your long, detailed response.
As a foreword to my answer I'd like to say that my proposal is not to
replace old good references with a custom template class, but to replace
std::shared_ptr in many cases with its non-nullable version. I feel
strong about making non-nullable pointer the default option with
nullable pointer as a special case. I don't consider regular pointers as
I believe that future programs will use them exceptionally rare.

> If a function takes a pointer type, and it cannot work if it is
> given a NULL pointer, then what has happened here is a violation of
> the implicit contract between the function and whomever called it.
> And a violation of this contract represents a program error, which
> means that the program is no longer in a reasonable state.

The point is to turn that implicit contract into an explicit one. Now it
can be frequently seen in functions' documentation comments:

     /// @param ptr: Pointer to something, must not be null.

This is the implicit contract, it is nice and easy, until you want to
actually enforce it. Doing that leads to great deal of problems, or you
will end up with a halted program.

> Therefore, the program needs to halt execution.

In some cases you don't really want the entire program to halt, but, for
example, to unload the plugin, cleanup after it and reload, while
sending the programmer a happy letter about an accident.

This is where your example runs into a bit of a problem.

Your interface takes this "never non-NULL smart pointer." OK. So to call your function, they must wrap their existing pointer in a "never non-NULL smart pointer" pointer.

So rather than this:

shard_ptr<T> p = ... //Pointer already existed somewhere.
CallThatCantHandleNULL(p);

You want:

shared_ptr<T> p = ...//Pointer already existed somewhere.
CallThatCantHandleNULL(std::object_ptr_cast(p));

So what happens if 'p' is NULL?

Oh sure, it's NULL on the caller's end rather than in your function. But the application stops just the same either way. Either they get an exception that they don't catch, or they get an exception that they do catch. It would have been no different on their end one way or another.

The only way this class solves the problem is if the user is always using it. If they used it from the moment they got that pointer, and passed it along to you that way.

But shared_ptr beat you to it. It's already there, with lots of people using it for memory management. Not to mention the innumerable books and teaching materials beating "shared_ptr" into people's heads. Without widespread adoption of the type, the problem doesn't really get fixed.
 
Never letting
such a situation happen due to just a missed null pointer seems to be a
better approach to me, as these days programmers have a lot of much more
serious problems then dealing with null pointers.

> If the user doesn't catch an exception, the application halts. If the
> application can't halt, then that's the problem of the user. That
> being said, I wouldn't throw an exception just for a NULL pointer. A
> pointer being not-NULL is a precondition, and a violation of a
> precondition should not result in an exception.
>
> It should result (in debug) in program termination.

And what would you do in release? "Live fast die young" is suitable not
for each program. Nobody is guaranteed against accidental invalid
pointer, the proposal is to eliminate the problem entirely.

> Also, "functions' throw lists" are no longer part of C++.

Okay, my fault. Thank to you for teaching me the difference between
"throw()" and "noexcept" — the former is deprecated :). But you you
still unable to mark function with "noexcept" keyword if you throw an
exception when checking nullable parameters.

Well, you shouldn't be marking functions "noexcept" frequently to begin with. But even if you want it to be noexcept, let it. noexcept doesn't mean "nothing gets thrown beyond this point". It means "if an exception gets past me, kill the application." Therefore, the onus would be on the implementer of the function to catch any exceptions that he doesn't want to terminate the app.


> If you're talking about an application that isn't allowed to
> terminate, then that means that your contract with the outside world
> needs to be permissive. Which means you have to accept NULL and
> define what should happen when you're given it. So you need to
> figure out how to do something innocuous if you get a bad parameter.

That permissiveness makes code paths more complex and can lead to
undesired consequences. For example, you have got invalid pointer as a
constructor parameters, what would you do? You cannot cancel object
construction,

Yes, you can. It's called "throwing an exception".
 
but you don't want to construct invalid object either. So
you have to add more logic to you class to distinguish between properly
constructed objects and failback-constructed objects. Or throw an
exception which you generally dislike to do.

Maybe you generally dislike it. But it's the correct way to handle invalid arguments passed to a constructor. We shouldn't add features to the language to support misuse of the language.

> And what if I already have a pointer, perhaps obtained via transfer
> of ownership from the outside world? Or if I need to use a special
> deleter on it?
>
> Why not just have the constructor that takes a pointer check for
> NULL and fail if it sees it? It can be explicit (just like
> shared_ptr's pointer constructor), so it can't accidentally acquire
> ownership.

We should definitely have the way to archive this, but I personally
would prefer to separate it from object_ptr class, just to make it
*extremely* clear for user that this is a point there ownership is
acquired or re-acquired.

But it is extremely clear; you're putting it in a smart pointer. The typename has to be explicitly stated if it's an explicit constructor.

It's kind of a big sign: 'std::shared_ref{p}'. You can't possibly miss it. It's certainly no more noticeable than 'std::shared_ref_cast(p)'

Given that, the object_ptr itself can stand free from checks and exception-raising.

But by doing it in the constructor, you can fix one of the problems you cited originally: the lack of a specific exception for the particular condition of a NULL pointer where there shouldn't be one.

You don't want to encourage people to write their own wrapper functions, which will all use independent exceptions.

I simply don't see an advantage of a free function here over an explicit constructor.


This is another reason why I suggest "shared_ref" rather than "object_ptr". Because what you're making when you combine it with optional is the infamous std::optional<T&>.

Only it's done in a way that's not terrible ;)

> I disagree with the idea that this is necessarily common (or at
> least, that it *has* to be). It is far more common to pass an object
> to a function without transferring ownership to it. In such cases, a
> reference is entirely appropriate.

I'm not about not using references there they should be.

> Given the above, if you're passing a pointer, then it's usually
> something that is one of the following: 1) can't be a reference (for
> some reason, like a naked C array or whatever), 2) is conceptually
> nullable (and therefore, the function is expecting to take a
> possibly null pointer), or 3) is a transfer of ownership. The cases
> you're worried about are #1 and #3.
>
> I don't think they happen often enough to require the creation of a
> whole new object type.

But they are often, at least the third one. If it wasn't, no smart
pointers would be introduced to the language at all.

There's a difference between "not often" and "never". We need smart pointers because we sometimes need to transfer ownership. But the overall goal should be to limit such times to the bare minimum required.

The vast majority of parameters are (or should be) taken by value or by reference. By smart pointer is, compared to all parameters used in an API, a relatively rare occurrence. Yes, it does happen. But it doesn't happen so frequently that you need a whole new type to cover "by smart pointer, previously assured to not be NULL".

Marc Mutz

unread,
Mar 9, 2015, 7:39:10 AM3/9/15
to std-pr...@isocpp.org, Pavel Kretov
On Sunday 08 March 2015 08:24:40 Pavel Kretov wrote:
> This is a common practice in programming to check input parameters for
> functions. As many parameters are pointers, smart or regular, checking
> them against "nullptr" is a very frequent operation, but what should
> you do if pointer is null? There are many options:

I was very much in favour of your idea until I read further. I thought you
were proposing a nonnull_ptr<T> which just wraps a T*, in the spriti of
Bjarne's Type-Rich Interface principle. But when I read "inheriting from
shared_ptr", I was disappointed.

What's wrong with

template <typename T>
class nonnull_ptr {
T *ptr;
public:
/* implicit */ nonnull_ptr(T *t) : ptr(t) { if (!t) std::terminate(); }
// or throw
// all you can do with a T*, except maybe pointer arithmetic
};

?

Thanks,
Marc

--
Marc Mutz <marc...@kdab.com> | Senior Software Engineer
KDAB (Deutschland) GmbH & Co.KG, a KDAB Group Company
www.kdab.com || Germany +49-30-521325470 || Sweden (HQ) +46-563-540090
KDAB - Qt Experts - Platform-Independent Software Solutions

Olaf van der Spek

unread,
Mar 9, 2015, 8:38:13 AM3/9/15
to std-pr...@isocpp.org


On Sunday, March 8, 2015 at 5:32:10 PM UTC+1, Nicol Bolas wrote:
If the user doesn't catch an exception, the application halts. If the application can't halt, then that's the problem of the user. That being said, I wouldn't throw an exception just for a NULL pointer. A pointer being not-NULL is a precondition, and a violation of a precondition should not result in an exception.

It should result (in debug) in program termination.

I agree, but what's the proper way to do that?
assert doesn't generally work in release / NDEBUG mode and we don't have a variant of assert that does.

I think that normal references are the answer to most of Pavel's problems with pointers. 
 

Olaf van der Spek

unread,
Mar 9, 2015, 8:44:04 AM3/9/15
to std-pr...@isocpp.org, jmck...@gmail.com
On Sunday, March 8, 2015 at 8:51:49 PM UTC+1, Pavel Kretov wrote:
I don't consider regular pointers as
I believe that future programs will use them exceptionally rare.

I think you're wrong. Regular pointers are fine as long as they don't own the pointee.
 
This is the implicit contract, it is nice and easy, until you want to
actually enforce it. Doing that leads to great deal of problems, or you
will end up with a halted program.

Most of the time a halted program is fine if bugs are encountered. Trying to deal with such bugs in other ways is error-prone.
 
> Therefore, the program needs to halt execution.

In some cases you don't really want the entire program to halt, but, for
example, to unload the plugin, cleanup after it and reload, while
sending the programmer a happy letter about an accident. Never letting
such a situation happen due to just a missed null pointer seems to be a
better approach to me, as these days programmers have a lot of much more
serious problems then dealing with null pointers.

Maybe, maybe not, but how would your non-nullable pointer solve this?
What does it do when you try to assign null anyway?
 
That permissiveness makes code paths more complex and can lead to
undesired consequences. For example, you have got invalid pointer as a
constructor parameters, what would you do? You cannot cancel object
construction, but you don't want to construct invalid object either. So
you have to add more logic to you class to distinguish between properly
constructed objects and failback-constructed objects. Or throw an
exception which you generally dislike to do.


Why can't object construction fail?
 

Olaf van der Spek

unread,
Mar 9, 2015, 8:47:16 AM3/9/15
to std-pr...@isocpp.org, jmck...@gmail.com


On Monday, March 9, 2015 at 12:01:05 AM UTC+1, Nicol Bolas wrote:
So I think what you're proposing would be better called "shared_ref". You'll also need "unique_ref", possibly "weak_ref", and certainly "observer_ref" as a typedef of "reference_wrapper" to complete the set.

What would weak_ref do if the pointee (referee?) is gone? 

Nicol Bolas

unread,
Mar 9, 2015, 9:49:28 AM3/9/15
to std-pr...@isocpp.org, firegu...@gmail.com


On Monday, March 9, 2015 at 7:39:10 AM UTC-4, Marc Mutz wrote:
On Sunday 08 March 2015 08:24:40 Pavel Kretov wrote:
> This is a common practice in programming to check input parameters for
> functions. As many parameters are pointers, smart or regular, checking
> them against "nullptr" is a very frequent operation, but what should
> you do if pointer is null? There are many options:

I was very much in favour of your idea until I read further. I thought you
were proposing a nonnull_ptr<T> which just wraps a T*, in the spriti of
Bjarne's Type-Rich Interface principle. But when I read "inheriting from
shared_ptr", I was disappointed.

What's wrong with

  template <typename T>
  class nonnull_ptr {
      T *ptr;
  public:
      /* implicit */ nonnull_ptr(T *t) : ptr(t) { if (!t) std::terminate(); }
// or throw
      // all you can do with a T*, except maybe pointer arithmetic
  };

?

Thanks,
Marc 
 
That already exists; it's called a reference.

What he wants is something that a language reference can't provide: transfer of ownership. He wants to transfer ownership of an object that certainly exists.

Nicol Bolas

unread,
Mar 9, 2015, 9:50:34 AM3/9/15
to std-pr...@isocpp.org


On Monday, March 9, 2015 at 8:38:13 AM UTC-4, Olaf van der Spek wrote:


On Sunday, March 8, 2015 at 5:32:10 PM UTC+1, Nicol Bolas wrote:
If the user doesn't catch an exception, the application halts. If the application can't halt, then that's the problem of the user. That being said, I wouldn't throw an exception just for a NULL pointer. A pointer being not-NULL is a precondition, and a violation of a precondition should not result in an exception.

It should result (in debug) in program termination.

I agree, but what's the proper way to do that?
assert doesn't generally work in release / NDEBUG mode and we don't have a variant of assert that does.

If you need to kill an application, call std::terminate. This isn't hard.

I think that normal references are the answer to most of Pavel's problems with pointers. 

As he specifically stated, he wants transfer-of-ownership semantics. And presently, you only get that with pointers.
 
 

Nicol Bolas

unread,
Mar 9, 2015, 9:53:08 AM3/9/15
to std-pr...@isocpp.org, jmck...@gmail.com

I did say probably ;)

It would return an optional<shared_ref<T>>. That has the same semantics as shared_ptr, only reference-style.

Olaf van der Spek

unread,
Mar 9, 2015, 10:59:22 AM3/9/15
to std-pr...@isocpp.org
2015-03-09 14:50 GMT+01:00 Nicol Bolas <jmck...@gmail.com>:
> If you need to kill an application, call std::terminate. This isn't hard.

It's not hard, but I prefer something like assure(!p); over if (!p)
std::terminate();
Do debuggers break if one calls terminate or abort?


--
Olaf

Pavel Kretov

unread,
Mar 9, 2015, 11:47:01 AM3/9/15
to Olaf van der Spek, std-pr...@isocpp.org
>> I don't consider regular pointers as
>> I believe that future programs will use them exceptionally rare.
>>
> I think you're wrong. Regular pointers are fine as long as they don't own
> the pointee.

Regular pointers are fine when used appropriately. But they don't seem
appropriate for me in cases where their use would lead to a bunch of
ownership semantics problems. I'm not a foreteller, but I can see the
movement toward using smart pointers in C++ community.

>> This is the implicit contract, it is nice and easy, until you want to
>> actually enforce it. Doing that leads to great deal of problems, or you
>> will end up with a halted program.
>
> Most of the time a halted program is fine if bugs are encountered. Trying
> to deal with such bugs in other ways is error-prone.

Not always. You would probably want to do something to recover your
program from every possible and impossible error if that program is
leading the rocket to an orbit. (Quite edgy example, though.)

>>> Therefore, the program needs to halt execution.
>>
>> In some cases you don't really want the entire program to halt, but, for
>> example, to unload the plugin, cleanup after it and reload, while
>> sending the programmer a happy letter about an accident. Never letting
>> such a situation happen due to just a missed null pointer seems to be a
>> better approach to me, as these days programmers have a lot of much more
>> serious problems then dealing with null pointers.
>
> Maybe, maybe not, but how would your non-nullable pointer solve this?
> What does it do when you try to assign null anyway?

You cannot simply assign non-nullable pointer a value, but if you try to
construct one with from a null, you would get an exception. But if your
program uses non-nullable pointers wherever possible, you would need to
control this exception very rarely.

>> That permissiveness makes code paths more complex and can lead to
>> undesired consequences. For example, you have got invalid pointer as a
>> constructor parameters, what would you do? You cannot cancel object
>> construction, but you don't want to construct invalid object either. So
>> you have to add more logic to you class to distinguish between properly
>> constructed objects and failback-constructed objects. Or throw an
>> exception which you generally dislike to do.
>>
> Why can't object construction fail?

We were talking about "permissive" behavior on validation fail. Unlike
ordinary functions, for constructors you can either construct an object,
or don't (by throwing an exception), you cannot do something innocent
and pretend nothing happened.

——— Pavel Kretov.

Pavel Kretov

unread,
Mar 9, 2015, 11:47:13 AM3/9/15
to Nicol Bolas, std-pr...@isocpp.org
> I think "object_ptr" is very much the wrong name. Why? A big part of the
> problem is that the name obliterates the most important word in its
> predecessor: *shared*. What ownership semantics does "object_ptr" have:
> shared, unique, none, something else? What would you name the "unique_ptr"
> analog to "object_ptr", or the "weak_ptr" equivalent? You'll still need
> those to complete your interface (well, maybe not "weak_ptr"; that one you
> can probably reuse).

Maybe you're right. I was thinking a lot about choosing appropriate name
for this entity. I started from "notnull_ptr", then thought of
"shared_ref", but later decided it would be nice to employ term "object"
in the meaning which I remember from reading C standard text:

«Object — a region of data storage in the execution environment,
the contents of which can represent values. Except for bit-fields,
objects are composed of contiguous sequences of one or more bytes,
the number, order, and encoding of which are either explicitly
specified or implementation-defined.»

But to my surprise, when I've checked C++ 2014 draft I did not find this
term. Name "shared_ref" seems to be appropriate for me, but we will be
unable to make it behave like real reference as we cannot overload
"operator.", it still will act like a pointer.

> It seems to me that what you are proposing is something analogous to
> std::reference_wrapper: a handle to an object that undeniably exists. You
> asked in your post how you have a reference-counted reference. Well, isn't
> that exactly what you're proposing?

Not exactly. I was thinking about the class just like shared_ptr, but
without all that tedious validation checks.

> Pointers are how you refer to objects that might not be there. References
> are how you refer to objects that *must* exist. Even though language
> references are not rebindable after creation, std::reference_wrapper is. So
> it's very much the same concept as what you want, only without the
> ownership semantics.

But without ownership semantics the whole proposal looses its meaning.

> In short, std::reference_wrapper is the std::observer_ptr of owning objects
> that have reference semantics (rather than pointer semantics).

Just for my information, is "observer_ptr" proposal accepted? I've seen
its draft only.

> So I think what you're proposing would be better called "shared_ref".
> You'll also need "unique_ref", possibly "weak_ref", and certainly
> "observer_ref" as a typedef of "reference_wrapper" to complete the set.

> Oh, you should still have explicit conversions from the equivalent _ptr
> types (obviously the "unique_" ones will require moves from their alternate
> types). And certainly, shared_ref should share memory with shared_ptr and
> vice-versa. They both ought to use the same back-end.

I was thinking about proposing such a companion type for "uniq_ptr" too
(I'm not sure about "weak_ptr"), but came to conclusion that "who would
store null in uniq_ptr?". May be I should think again, as the symmetry
you just shown would be quite nice.

> The only difference is that, if you hand them a NULL pointer, they'll throw
> something.

There is std::bad_weak_ptr already, maybe we should introduce just
"bad_pointer", or "bad_smart_pointer", or "bad_reference_cast"?

>> In some cases you don't really want the entire program to halt, but, for
>> example, to unload the plugin, cleanup after it and reload, while
>> sending the programmer a happy letter about an accident.
>
> This is where your example runs into a bit of a problem.
>
> Your interface takes this "never non-NULL smart pointer." OK. So to call
> your function, they must wrap their existing pointer in a "never non-NULL
> smart pointer" pointer.
>
> So rather than this:
>
> shared_ptr<T> p = ... //Pointer already existed somewhere.
> CallThatCantHandleNULL(p);
>
> You want:
>
> shared_ptr<T> p = ...//Pointer already existed somewhere.
> CallThatCantHandleNULL(std::object_ptr_cast(p));
>
> So what happens if 'p' is NULL?

I think an exception must be thrown. This is essential for this cast to
be called as rare as possible, only for compatibility with third-party
or legacy code, thus reducing number of points where such an exceptions
may be thrown. I'd prefer to perform this cast as a function (not as a
constructor) for user to be able easily locate such points in their code
(using grep or something like this).

If "shared_refs" are used in your code base consistently, as widely as
possible, employing std::optional instead of shared_ptr, then you would
hardly ever need to call that "cast" function except for legacy purposed.

> Oh sure, it's NULL on the caller's end rather than in your function. But
> the application stops just the same either way. Either they get an
> exception that they don't catch, or they get an exception that they do
> catch. It would have been no different on their end one way or another.

Yes, but the overall number of places there you catch these exceptions
would greatly reduce, as you are not obliged to try{} function calls,
but only the places where you create "shared_refs" from nullable pointers.

> The only way this class solves the problem is if the user is *always* using
> it. If they used it from the moment they got that pointer, and passed it
> along to you that way.
> But shared_ptr beat you to it. It's already there, with lots of people
> using it for memory management. Not to mention the innumerable books and
> teaching materials beating "shared_ptr" into people's heads. Without
> widespread adoption of the type, the problem doesn't really get fixed.

You're freaking perfectly right. That is the sad part of story and the
reason why I wrote my proposal into the mailing list associated with C++
standard committee instead of my Facebook page. :)

> Well, you shouldn't be marking functions "noexcept" frequently to begin
> with. But even if you want it to be noexcept, let it. noexcept doesn't mean
> "nothing gets thrown beyond this point". It means "if an exception gets
> past me, kill the application." Therefore, the onus would be on the
> implementer of the function to catch any exceptions that he doesn't want to
> terminate the app.

Thank you once again for your explanation. I liked the idea of throw
lists a lot, given that that lists are to be checked by the compiler
whether possible. "Noexcept" seems to be quite different. It seems I
must read my C++ book once again.

>> That permissiveness makes code paths more complex and can lead to
>> undesired consequences. For example, you have got invalid pointer as a
>> constructor parameters, what would you do? You cannot cancel object
>> construction,
>
> Yes, you can. It's called "throwing an exception".

By "cancel" I mean early returning from a function (possibly with an
error code), but for constructors there is no such option.

>> but you don't want to construct invalid object either. So
>> you have to add more logic to you class to distinguish between properly
>> constructed objects and failback-constructed objects. Or throw an
>> exception which you generally dislike to do.
>
> Maybe *you* generally dislike it. But it's the correct way to handle
> invalid arguments passed to a constructor. We shouldn't add features to the
> language to support misuse of the language.

This was a kind of answer to your "I'd prefer not throw an exception
just due to NULL pointer" (inaccurate quotation).

>> We should definitely have the way to archive this, but I personally
>> would prefer to separate it from object_ptr class, just to make it
>> *extremely* clear for user that this is a point there ownership is
>> acquired or re-acquired.
>
> But it is extremely clear; you're putting it in a *smart pointer*. The
> typename has to be explicitly stated if it's an explicit constructor.
>
> It's kind of a big sign: 'std::shared_ref{p}'. You can't possibly miss it.
> It's certainly no more noticeable than 'std::shared_ref_cast(p)'

Maybe you're right. This would be consistent with existing "shared_ptr"
behavior.

> But by doing it in the constructor, you can fix one of the problems you
> cited originally: the lack of a specific exception for the particular
> condition of a NULL pointer where there shouldn't be one.
>
> You don't want to encourage people to write their own wrapper functions,
> which will all use independent exceptions.

Certainly I don't. But even in constructor, which exception should I
throw when null pointer is passed?

> This is another reason why I suggest "shared_ref" rather than "object_ptr".
> Because what you're making when you combine it with optional is the
> infamous std::optional<T&>.
>
> Only it's done in a way that's not terrible ;)

Okay, way too many points to rename.

> The vast majority of parameters are (or should be) taken by value or by
> reference. By smart pointer is, compared to all parameters used in an API,
> a relatively rare occurrence. Yes, it does happen. But it doesn't happen so
> frequently that you need a whole new type to cover "by smart pointer,
> previously assured to not be NULL".

Maybe. (This was on "need a whole new type", not on "should be
[preferably] taken by value or reference".) But, as you see from our
discussion, how do you, personally, think, should I prepare the official
proposal or the whole idea is doomed?

——— Pavel Kretov.

Thiago Macieira

unread,
Mar 9, 2015, 12:05:36 PM3/9/15
to std-pr...@isocpp.org
On Monday 09 March 2015 15:59:19 Olaf van der Spek wrote:
> Do debuggers break if one calls terminate or abort?

Yes, they usually do.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Olaf van der Spek

unread,
Mar 9, 2015, 12:41:17 PM3/9/15
to Pavel Kretov, std-pr...@isocpp.org
2015-03-09 16:43 GMT+01:00 Pavel Kretov <firegu...@gmail.com>:
>>> I don't consider regular pointers as
>>> I believe that future programs will use them exceptionally rare.
>>>
>> I think you're wrong. Regular pointers are fine as long as they don't own
>> the pointee.
>
>
> Regular pointers are fine when used appropriately. But they don't seem
> appropriate for me in cases where their use would lead to a bunch of
> ownership semantics problems. I'm not a foreteller, but I can see the
> movement toward using smart pointers in C++ community.

That does not contradict my statement.

> Not always. You would probably want to do something to recover your program
> from every possible and impossible error if that program is leading the
> rocket to an orbit. (Quite edgy example, though.)

I don't know, I'm not a rocket scientist. Are you?
Exception and dynamic memory are probably forbidden in those cases anyway..

Rockets might self-destruct or call a fatal error handler too, I don't
think they try to somehow ignore the error.

> You cannot simply assign non-nullable pointer a value, but if you try to
> construct one with from a null, you would get an exception. But if your
> program uses non-nullable pointers wherever possible, you would need to
> control this exception very rarely.

IMO the same is true for using normal smart pointers and normal
references. Issues with null pointers just don't come up often.

--
Olaf

Olaf van der Spek

unread,
Mar 9, 2015, 12:45:11 PM3/9/15
to std-pr...@isocpp.org
2015-03-09 17:05 GMT+01:00 Thiago Macieira <thi...@macieira.org>:
> On Monday 09 March 2015 15:59:19 Olaf van der Spek wrote:
>> Do debuggers break if one calls terminate or abort?
>
> Yes, they usually do.

$ ./a.out
Aborted

$ ./a.out
terminate called without an active exception
Aborted

$ ./a.out
a.out: b.cpp:6: int main(): Assertion `false' failed.
Aborted

I like the output of assert() better. Might be a toolchain issue though.


--
Olaf

Nicol Bolas

unread,
Mar 9, 2015, 12:46:39 PM3/9/15
to std-pr...@isocpp.org, jmck...@gmail.com
On Monday, March 9, 2015 at 11:47:13 AM UTC-4, Pavel Kretov wrote:
> I think "object_ptr" is very much the wrong name. Why? A big part of the
> problem is that the name obliterates the most important word in its
> predecessor: *shared*. What ownership semantics does "object_ptr" have:
> shared, unique, none, something else? What would you name the "unique_ptr"
> analog to "object_ptr", or the "weak_ptr" equivalent? You'll still need
> those to complete your interface (well, maybe not "weak_ptr"; that one you
> can probably reuse).

Maybe you're right. I was thinking a lot about choosing appropriate name
for this entity. I started from "notnull_ptr", then thought of
"shared_ref", but later decided it would be nice to employ term "object"
in the meaning which I remember from reading C standard text:

     «Object — a region of data storage in the execution environment,
      the contents of which can represent values. Except for bit-fields,
      objects are composed of contiguous sequences of one or more bytes,
      the number, order, and encoding of which are either explicitly
      specified or implementation-defined.»

But to my surprise, when I've checked C++ 2014 draft I did not find this
term. Name "shared_ref" seems to be appropriate for me, but we will be
unable to make it behave like real reference as we cannot overload
"operator.", it still will act like a pointer.

You use it like it's a pointer. But outside of having to use ->, it will behave like a reference: it will always refer to an object.

> Pointers are how you refer to objects that might not be there. References
> are how you refer to objects that *must* exist. Even though language
> references are not rebindable after creation, std::reference_wrapper is. So
> it's very much the same concept as what you want, only without the
> ownership semantics.

But without ownership semantics the whole proposal looses its meaning.

I meant that std::reference_wrapper has no ownership semantics.

I also thought that std::reference_wrapper had an operator-> overload. It doesn't.

> The only way this class solves the problem is if the user is *always* using
> it. If they used it from the moment they got that pointer, and passed it
> along to you that way.
> But shared_ptr beat you to it. It's already there, with lots of people
> using it for memory management. Not to mention the innumerable books and
> teaching materials beating "shared_ptr" into people's heads. Without
> widespread adoption of the type, the problem doesn't really get fixed.

You're freaking perfectly right. That is the sad part of story and the
reason why I wrote my proposal into the mailing list associated with C++
standard committee instead of my Facebook page. :)

My point is that having the class and sticking it in a few interface functions doesn't fix the problem. It has to be used consistently and everywhere. And existing smart-pointers are already being widely used, so it's an uphill battle to get everyone to suddenly use another reference-counted object.

> Well, you shouldn't be marking functions "noexcept" frequently to begin
> with. But even if you want it to be noexcept, let it. noexcept doesn't mean
> "nothing gets thrown beyond this point". It means "if an exception gets
> past me, kill the application." Therefore, the onus would be on the
> implementer of the function to catch any exceptions that he doesn't want to
> terminate the app.

Thank you once again for your explanation. I liked the idea of throw
lists a lot, given that that lists are to be checked by the compiler
whether possible. "Noexcept" seems to be quite different. It seems I
must read my C++ book once again.

No, it isn't. The same thing would happen in throw lists too (assuming your compiler actually implemented them). If an exception other than a listed one tried to escape the function, then std::terminate would be called.
 
> The vast majority of parameters are (or should be) taken by value or by
> reference. By smart pointer is, compared to all parameters used in an API,
> a relatively rare occurrence. Yes, it does happen. But it doesn't happen so
> frequently that you need a whole new type to cover "by smart pointer,
> previously assured to not be NULL".

Maybe. (This was on "need a whole new type", not on "should be
[preferably] taken by value or reference".) But, as you see from our
discussion, how do you, personally, think, should I prepare the official
proposal or the whole idea is doomed?

As I think about it, there has to be a better solution for this problem than to create a mirror of all of the smart pointer types. After all, the only difference between the pointer and reference versions is that the reference will throw an exception if it is fed a NULL pointer value. And what of people who use an intrusive_ptr class or something; they would have to write their own intrusive_ref.

Maybe you could have some template class, smart_ref, which takes a (presumably smart) pointer type. So you would have smart_ref<shared_ptr<T>>. Of course, you can create typedefs for standard smart pointers:

template<typename T> using shared_ref = smart_ref<shared_ptr<T>>;
template<typename T, typename D = std::default_delete<T>> using unique_ref = smart_ref<unique_ptr<T, D>>;

Matthew Woehlke

unread,
Mar 9, 2015, 1:59:25 PM3/9/15
to std-pr...@isocpp.org
On 2015-03-09 11:46, Pavel Kretov wrote:
> I was thinking about proposing such a companion type for "uniq_ptr" too
> (I'm not sure about "weak_ptr"), but came to conclusion that "who would
> store null in uniq_ptr?".

FWIW... I would. I have an application with a number of optional
components. For obvious reasons, I don't want to have to think too hard
about memory management, so if the component is used, I want to store
the pointer in a std::unique_ptr, but if it is not used, I just leave
the std::unique_ptr default-initialized (i.e. null). Of course, that
would be similar to std::optional<std::unique_ref>.

(In the real application I think I'm using either QScopedPointer, which
is for all intents and purposes the Qt equivalent of std::unique_ptr, or
else relying on QObject hierarchies to reap the object, but the point
still stands.)

--
Matthew

Thiago Macieira

unread,
Mar 9, 2015, 2:52:13 PM3/9/15
to std-pr...@isocpp.org
assert() is a macro, so it can record[*] the file name and line numbers of the
assertion. std::terminate() is a function, it can't do that.

If you want to get more information, run in the debugger and get the stack
trace.

[*] until you get customers asking you why their file names and function names
are leaking in the binary they created using your tools. See
https://codereview.qt-project.org/95627.

Olaf van der Spek

unread,
Mar 9, 2015, 3:07:12 PM3/9/15
to std-pr...@isocpp.org
2015-03-09 19:52 GMT+01:00 Thiago Macieira <thi...@macieira.org>:
> On Monday 09 March 2015 17:45:09 Olaf van der Spek wrote:
>> 2015-03-09 17:05 GMT+01:00 Thiago Macieira <thi...@macieira.org>:
>> > On Monday 09 March 2015 15:59:19 Olaf van der Spek wrote:
>> >> Do debuggers break if one calls terminate or abort?
>> >
>> > Yes, they usually do.
>>
>> $ ./a.out
>> Aborted
>>
>> $ ./a.out
>> terminate called without an active exception
>> Aborted
>>
>> $ ./a.out
>> a.out: b.cpp:6: int main(): Assertion `false' failed.
>> Aborted
>>
>> I like the output of assert() better. Might be a toolchain issue though.
>
> assert() is a macro, so it can record[*] the file name and line numbers of the
> assertion. std::terminate() is a function, it can't do that.

I'm sure some compiler magic can fix that.

> If you want to get more information, run in the debugger and get the stack
> trace.

Not so simple on hard to reproduce bugs.

> [*] until you get customers asking you why their file names and function names
> are leaking in the binary they created using your tools. See
> https://codereview.qt-project.org/95627.

Sure, but what's your point?



--
Olaf

Marc Mutz

unread,
Mar 9, 2015, 3:18:33 PM3/9/15
to std-pr...@isocpp.org, Nicol Bolas
On Monday 09 March 2015 14:49:28 Nicol Bolas wrote:
> That already exists; it's called a *reference*.

Yes, yes, the expected knee-jerk reaction... :)

"We don't need a Speed class", said the NASA engineer, "because that already
exists: it's called a *double*."

So tell me: since when can references be re-seated?

A reference is *not* a non-nullable pointer.

As for ownership: One could specialise nonnull_ptr for unique_ptr and
shared_ptr. Or have a nonnull template that can be specialised for other
types, too. It would be a big step in the direction of type-rich interfaces.

Cleiton Santoia

unread,
Mar 9, 2015, 5:27:25 PM3/9/15
to std-pr...@isocpp.org

I understand the point "avoid id to receive a nullptr in a function" ( therefore not need to check )

int foo(const object_ptr<int>& i) {
   
return (int)i * 2;  // nice boy, does not need to check !
}

however, I have a few doubts:

object_ptr<int> caller(int *p, std::shared<int> sp) {
  int *a = new int(1);
 
int b = 2;

  foo
(a);  // should be ok ( even when a is a local )
  foo
(&b); // should be ok ( even when b is a local )

// doubts:
  foo
(p);  // doubt 1 : should check for p==nullptr, if true, then what ? throw an exception ? build a default int ?
  foo
(b);  // doubt 2 : this is possible ? since ( b is not a pointer )
  foo
(3);  // doubt 3 : this is possible ? since 3 is an int const and foo parm also a const;
  foo
(sp);
// doubt 4 : should check for sp==nullptr, if true, then what ? throw an exception ? build a default int ?

 
return &b; // doubt 5 : what happen here ? since b is local.
}

BR
Cleiton

Nicol Bolas

unread,
Mar 9, 2015, 7:09:06 PM3/9/15
to std-pr...@isocpp.org, jmck...@gmail.com
On Monday, March 9, 2015 at 3:18:33 PM UTC-4, Marc Mutz wrote:
On Monday 09 March 2015 14:49:28 Nicol Bolas wrote:
> That already exists; it's called a *reference*.

Yes, yes, the expected knee-jerk reaction... :)

Yes; the right answer often is predictable.

"We don't need a Speed class", said the NASA engineer, "because that already
exists: it's called a *double*."

I'm fairly sure they don't have a Speed class now either.
 
So tell me: since when can references be re-seated?

A reference is *not* a non-nullable pointer.

auto foo = std::ref(bar);
foo = std::ref(blah);

References look pretty reseatable to me. Unless you're saying that std::reference_wrapper is poorly named?

The fact that language references can't be reseated doesn't change the fact that reference semantics are functionally identical to non-nullable pointers.

Pavel Kretov

unread,
Mar 10, 2015, 9:47:57 AM3/10/15
to std-pr...@isocpp.org
>> "We don't need a Speed class", said the NASA engineer, "because that
>> already exists: it's called a *double*."
>
> I'm fairly sure they don't have a Speed class now either.

Maybe they use boost::units, as the idea to explicitly assign units to
quantities seems to be perfectly suitable for rocket science. Or maybe
they have already developed their own version of C++ for that, who knows.

——— P. K.

Pavel Kretov

unread,
Mar 10, 2015, 11:18:47 AM3/10/15
to Olaf van der Spek, std-pr...@isocpp.org
>>>> I don't consider regular pointers as
>>>> I believe that future programs will use them exceptionally rare.
>>>
>>> I think you're wrong. Regular pointers are fine as long as they don't own
>>> the pointee.
>>
>> Regular pointers are fine when used appropriately. But they don't seem
>> appropriate for me in cases where their use would lead to a bunch of
>> ownership semantics problems. I'm not a foreteller, but I can see the
>> movement toward using smart pointers in C++ community.
>
> That does not contradict my statement.

Okay, I'll try to say another way. I think that the number if places,
where you cannot use references, but can use pointers without worrying
about ownership, are rare. But anyway, there is no point in arguing as
we're talking about the future.

>> Not always. You would probably want to do something to recover your program
>> from every possible and impossible error if that program is leading the
>> rocket to an orbit. (Quite edgy example, though.)
>
> I don't know, I'm not a rocket scientist. Are you?
> Exception and dynamic memory are probably forbidden in those cases anyway..

I'm not the rocket one, what is why I called the example "edgy".
Probably they forbid dynamic memory and exceptions (I'm pretty sure,
indeed), but what I know is that for writing responsible systems many
rules are often enforced as a convention ("We do not use exceptions in
rocket control module, return -1 instead"), but who said that none of
them are old or even plain wrong. Moreover, conventions are bad when
they're not automatically enforced.

> Rockets might self-destruct or call a fatal error handler too, I don't
> think they try to somehow ignore the error.

In the worst case they could launch a missile and take the rocket down.
But not due to null pointer exception, I hope.

>> You cannot simply assign non-nullable pointer a value, but if you try to
>> construct one with from a null, you would get an exception. But if your
>> program uses non-nullable pointers wherever possible, you would need to
>> control this exception very rarely.
>
> IMO the same is true for using normal smart pointers and normal
> references. Issues with null pointers just don't come up often.

It depends. To my experience, when the system is decoupled into many
simple classes, almost every class has at least one pointer parameter in
one of its interface methods.

——— P. K.

Thiago Macieira

unread,
Mar 10, 2015, 11:46:18 AM3/10/15
to std-pr...@isocpp.org
On Monday 09 March 2015 20:07:10 Olaf van der Spek wrote:
> > [*] until you get customers asking you why their file names and function
> > names are leaking in the binary they created using your tools. See
> > https://codereview.qt-project.org/95627.
>
> Sure, but what's your point?

Just that file names and line numbers are overrated and you may not want to
have them at all assertion & termination points, since that may leak
information you don't want other people to have.

Olaf van der Spek

unread,
Mar 10, 2015, 2:50:20 PM3/10/15
to std-pr...@isocpp.org
2015-03-10 16:46 GMT+01:00 Thiago Macieira <thi...@macieira.org>:
> On Monday 09 March 2015 20:07:10 Olaf van der Spek wrote:
>> > [*] until you get customers asking you why their file names and function
>> > names are leaking in the binary they created using your tools. See
>> > https://codereview.qt-project.org/95627.
>>
>> Sure, but what's your point?
>
> Just that file names and line numbers are overrated and you may not want to
> have them at all assertion & termination points, since that may leak
> information you don't want other people to have.

Sure, putting file/function names and line numbers into a (release)
executable might not be the smartest thing, but maybe it's exactly
what you want.


--
Olaf

Pavel Kretov

unread,
Mar 10, 2015, 3:06:15 PM3/10/15
to std-pr...@isocpp.org
> Just that file names and line numbers are overrated and you may not want to
> have them at all assertion & termination points, since that may leak
> information you don't want other people to have.

What is so secret about file names? (Apart from revealing to the world
the fact that you, for example, is actually linking GPLed library into
your proprietary application?) File names are just file names.

——— P. K.


Matheus Izvekov

unread,
Mar 10, 2015, 4:31:37 PM3/10/15
to std-pr...@isocpp.org
They may save you some time when doing reverse engineering.
It gives you some context.

Cleiton Santoia

unread,
Mar 12, 2015, 5:24:54 PM3/12/15
to std-pr...@isocpp.org
Mr Kretov, I agree with Thiago,

I think that is really important be able to NOT pass this information if you don't wan to, 
but I´m traversing the conversation just to say that yet have to answer my previous questions please :)

Cleiton

Pavel Kretov

unread,
Mar 12, 2015, 5:58:37 PM3/12/15
to Cleiton Santoia, std-pr...@isocpp.org
> but I´m traversing the conversation just to say that yet have to
> answer my previous questions please :)

Sorry, I've missed your letter a bit. I'm now thinking about what I'm
going to do with the proposal, prepare paper or not, and looked the list
not very carefully.

> however, I have a few doubts:
>
> object_ptr<int> caller(int *p, std::shared<int> sp) { int *a = new
> int(1); int b = 2;
>
> foo(a); // should be ok ( even when a is a local )

In fact, not okay. There is no way for "object_ptr" to know the fact
that "a" was obtained with "new" and thus cannot hold null. You should
write instead:

auto a = std::make_object<int>();

> foo(&b); // should be ok ( even when b is a local )

Also not okay, the same reason. But in that particular situation you
should use plain references (int&).

> foo(p); // doubt 1 : should check for p==nullptr, if true, then what?
> throw an exception? build a default int?

Also would not compile. "p" is a plain pointer which can possibly hold
null value, you have to explicitly obtain object_ptr from it:

foo(object_ptr(p));

which would throw and exception if the pointer is invalid.

> foo(b); // doubt 2 : this is possible ? since (b is not a pointer)

Would not compile, not implicit conversion from "int". In fact, you
really don't need that conversion to be implicit.

> foo(3); // doubt 3 : this is possible ? since 3 is an int const and
> foo

The same thing.

> foo(sp); // doubt 4 : should check for sp==nullptr, if true, then
> what ?

Would not compile. Nullable shared pointer must be explicitly converted
to non-nullable.

> throw an exception ? build a default int ?

Yes, it is supposed to throw an exception if this explicit conversion
detects null pointer.

> return &b; // doubt 5 : what happen here ? since b is local.

Would not compile as conversion is explicit. But if you insist on
foot-shooting, use "object_ptr(&b)", which will be runtime error.

In fact, looking at your questions, I started thinking that maybe my
proposal is wrong, as the language is already very freaking complex,
and, moreover, the very idea of smart pointers haven't yet been fully
accepted by many developers' minds.

——— P. K.

Cleiton Santoia

unread,
Mar 12, 2015, 6:30:36 PM3/12/15
to std-pr...@isocpp.org, cleiton...@gmail.com
Don't worry I got some solution to this but was a too rubbish to propose.

Lately I use more and more a "sentinela(portuguese)", probably "sentry" in english technique.
When you are implementing a double linked list, is better to spare two allocations and build a dummy ( or sentry ) head and tail, so you never point head and tail to nullptr and you never need to verify this in the code.

So i came up with a 

ensured_shared_ptr<T,deleter,dummy_nullptr_instance_allocator>

whenever you need to dereference a nullptr, the "ensured_shared_ptr" ensures one valid to you, by allocating via dummy_nullptr_instance_allocator, when you create a instance of T (probably descendant of T) that only drop logs on each call and do some house keeping enough to not break the system.

Clearly this has a lot of other problems, you get more instances that what you need, sometimes is better to stop processing or throw an exception, and not aways is possible to get a class T that will handle gracefully a dummy value, but in some cases it´s really beter;

Cleiton

Marc Mutz

unread,
Mar 17, 2015, 5:30:54 AM3/17/15
to std-pr...@isocpp.org, Nicol Bolas
On Tuesday 10 March 2015 00:09:05 Nicol Bolas wrote:
> > So tell me: since when can references be re-seated?
> >
> > A reference is not a non-nullable pointer.
>
> auto foo = std::ref(bar);
> foo = std::ref(blah);
>
> References look pretty reseatable to me. Unless you're saying that
> std::reference_wrapper is poorly named?
>
> The fact that language references can't be reseated doesn't change the
> fact that reference semantics are functionally identical to non-nullable
> pointers.

std::reference_wrapper is a _reference_ wrapper. It doesn't model a _pointer_.

I'm looking for a way to say clearly and unambiguously that a function expects
a non-null pointer. std::reference_wrapper doesn't fit the bill, for at least
two reasons: Use of it in APIs is surprising absent any text book (that I know
of) introducing it as a pattern for a non-null pointer. Even if that was a
pattern, it would suffer from the ambiguity that there might be other valid
reasons to accept a reference_wrapper in the API[1]. non_nullptr<T> says
exactly and unambiguously that the argument is a non-null pointer. You
wouldn't use a Point2D to represent Size2D even if both (and std::complex) are
layout-compatible to pair<T, T>.

For a concrete example: how would std:.reference_wrapper help with expressing
the requirement that std::basic_string(const CharT*) requires a non-null
argument?

Thanks,
Marc

[1] e.g. std::begin() could do with one, so you could force range-for to use
const_iterator by saying for (i: cref(vec)) - that's important for (broken)
designs in which containers are COW.

--
Marc Mutz <marc...@kdab.com> | Senior Software Engineer
KDAB (Deutschland) GmbH & Co.KG, a KDAB Group Company
Tel: +49-30-521325470
KDAB - The Qt Experts

Thiago Macieira

unread,
Mar 17, 2015, 1:38:36 PM3/17/15
to std-pr...@isocpp.org
On Tuesday 17 March 2015 10:35:50 Marc Mutz wrote:
> For a concrete example: how would std:.reference_wrapper help with
> expressing the requirement that std::basic_string(const CharT*) requires a
> non-null argument?

Non-example if we had used reference_wrapper:

std::string foo(*"some text");

Nevin Liber

unread,
Mar 17, 2015, 2:04:10 PM3/17/15
to std-pr...@isocpp.org
On 17 March 2015 at 04:35, Marc Mutz <marc...@kdab.com> wrote:
For a concrete example: how would std:.reference_wrapper help with expressing
the requirement that std::basic_string(const CharT*) requires a non-null
argument?

That isn't the requirement.  From N4296 [string.cons] p8:

 Requires: s points to an array of at least traits::length(s) + 1 elements of charT.


 How are you going to model that in the type system?  Just modeling non-nullable pointer is just a half-measure.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Marc Mutz

unread,
Mar 17, 2015, 4:00:33 PM3/17/15
to std-pr...@isocpp.org, Nevin Liber
On Tuesday 17 March 2015 19:03:28 Nevin Liber wrote:
> On 17 March 2015 at 04:35, Marc Mutz <marc...@kdab.com> wrote:
> > For a concrete example: how would std:.reference_wrapper help with
> > expressing
> > the requirement that std::basic_string(const CharT*) requires a non-null
> > argument?
>
> That isn't the requirement.

*shrug*

Sure it's not _the whole_ requirements, but a necessary subset. And it's
usually the case that the type system only checks for a necessary subset of a
function's preconditions.

> How are you going to model that in the type system? Just modeling
> non-nullable pointer is just a half-measure.

Just because in general you cannot check all preconditions statically doesn't
mean you shouldn't check the necessary subset that can be represented in the
type system. E.g. you often require that a non-null pointer, points to a valid
object. But that's usually implied for non-void*/char*s.

And when your function accepts some form of Speed type, you will also usually
require that the value isn't nan, or infinity.

This stuff doesn't have to be perfect. Just better is enough.

Thanks,
Marc

george...@gmail.com

unread,
Nov 21, 2015, 5:24:49 AM11/21/15
to ISO C++ Standard - Future Proposals
I completely support this idea and its concept may even become part of my scientific work. The only problem is to find appropriate, meaningful, consistent names for non-nullable pointers.

Nulls (None's, nil's) are headache in C++, C#, Java, Python, Objective C and so on.

Contract should be as explicit as possible. The earlier arguments are checked the less harmful potential consequences are.
Reply all
Reply to author
Forward
0 new messages