758. shared_ptr and nullptr

Section: 23.11.3 [util.smartptr.shared] Status: C++11 Submitter: Joe Gottman Opened: 2007-10-31 Last modified: 2016-02-10

Priority: Not Prioritized

View other active issues in [util.smartptr.shared].

View all other issues in [util.smartptr.shared].

View all issues with C++11 status.

Discussion:

Consider the following program:

int main() {
   shared_ptr<int> p(nullptr); 
   return 0;
}

This program will fail to compile because shared_ptr uses the following template constructor to construct itself from pointers:

template <class Y> shared_ptr(Y *);

According to N2431, the conversion from nullptr_t to Y * is not deducible, so the above constructor will not be found. There are similar problems with the constructors that take a pointer and a deleter or a pointer, a deleter and an allocator, as well as the corresponding forms of reset(). Note that N2435 will solve this problem for constructing from just nullptr, but not for constructors that use deleters or allocators or for reset().

In the case of the functions that take deleters, there is the additional question of what argument should be passed to the deleter when it is eventually called. There are two reasonable possibilities: nullptr or static_cast<T *>(0), where T is the template argument of the shared_ptr. It is not immediately clear which of these is better. If D::operator() is a template function similar to shared_ptr's constructor, then d(static_cast<T*>(0)) will compile and d(nullptr) will not. On the other hand, if D::operator()() takes a parameter that is a pointer to some type other that T (for instance U* where U derives from T) then d(nullptr) will compile and d(static_cast<T *>(0)) may not.

[ Bellevue: ]

The general idea is right, we need to be able to pass a nullptr to a shared_ptr, but there are a few borderline editorial issues here. (For example, the single-argument nullptr_t constructor in the class synopsis isn't marked explicit, but it is marked explicit in the proposed wording for 20.6.6.2.1. There is a missing empty parenthesis in the form that takes a nullptr_t, a deleter, and an allocator.)

More seriously: this issue says that a shared_ptr constructed from a nullptr is empty. Since "empty" is undefined, it's hard to know whether that's right. This issue is pending on handling that term better.

Peter suggests definition of empty should be "does not own anything"

Is there an editorial issue that post-conditions should refer to get() = nullptr, rather than get() = 0?

No strong feeling towards accept or NAD, but prefer to make a decision than leave it open.

Seems there are no technical merits between NAD and Ready, comes down to "Do we intentially want to allow/disallow null pointers with these functions". Staw Poll - support null pointers 5 - No null pointers 0

Move to Ready, modulo editorial comments

[ post Bellevue Peter adds: ]

The following wording changes are less intrusive:

In 23.11.3.1 [util.smartptr.shared.const], add:

shared_ptr(nullptr_t);

after:

shared_ptr();

(Absence of explicit intentional.)

px.reset( nullptr ) seems a somewhat contrived way to write px.reset(), so I'm not convinced of its utility.

It's similarly not clear to me whether the deleter constructors need to be extended to take nullptr, but if they need to:

Add

template<class D> shared_ptr(nullptr_t p, D d);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

after

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);

Note that this changes the semantics of the new constructors such that they consistently call d(p) instead of d((T*)0) when p is nullptr.

The ability to be able to pass 0/NULL to a function that takes a shared_ptr has repeatedly been requested by users, but the other additions that the proposed resolution makes are not supported by real world demand or motivating examples.

It might be useful to split the obvious and non-controversial nullptr_t constructor into a separate issue. Waiting for "empty" to be clarified is unnecessary; this is effectively an alias for the default constructor.

[ Sophia Antipolis: ]

We want to remove the reset functions from the proposed resolution.

The remaining proposed resolution text (addressing the constructors) are wanted.

Disposition: move to review. The review should check the wording in the then-current working draft.

Proposed resolution:

In 23.11.3 [util.smartptr.shared] p4, add to the definition/synopsis of shared_ptr:

template<class D> shared_ptr(nullptr_t p, D d);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

after

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);

In 23.11.3.1 [util.smartptr.shared.const] add:

template<class D> shared_ptr(nullptr_t p, D d);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

after

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);

(reusing the following paragraphs 23.11.3.1 [util.smartptr.shared.const]/9-13 that speak of p.)

In 23.11.3.1 [util.smartptr.shared.const]/10, change

Effects: Constructs a shared_ptr object that owns the pointer object p and the deleter d. The second constructor shall use a copy of a to allocate memory for internal use.

Rationale:

[ San Francisco: ]

"pointer" is changed to "object" to handle the fact that nullptr_t isn't a pointer.