Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
214 views
in Technique[技术] by (71.8m points)

c++ - Why does std::unique_ptr operator* throw and operator-> does not throw?

In the C++ standard draft (N3485), it states the following:

20.7.1.2.4 unique_ptr observers [unique.ptr.single.observers]

typename add_lvalue_reference<T>::type operator*() const;

1 Requires: get() != nullptr.
2 Returns: *get().

pointer operator->() const noexcept;

3 Requires: get() != nullptr.
4 Returns: get().
5 Note: use typically requires that T be a complete type.

You can see that operator* (dereference) is not specified as noexcept, probably because it can cause a segfault, but then operator-> on the same object is specified as noexcept. The requirements for both are the same, however there is a difference in exception specification.

I have noticed they have different return types, one returns a pointer and the other a reference. Is that saying that operator-> doesn't actually dereference anything?

The fact of the matter is that using operator-> on a pointer of any kind which is NULL, will segfault (is UB). Why then, is one of these specified as noexcept and the other not?

I'm sure I've overlooked something.

EDIT:

Looking at std::shared_ptr we have this:

20.7.2.2.5 shared_ptr observers [util.smartptr.shared.obs]

T& operator*() const noexcept;

T* operator->() const noexcept;

It's not the same? Does that have anything to do with the different ownership semantics?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

A segfault is outside of C++'s exception system. If you dereference a null pointer, you don't get any kind of exception thrown (well, atleast if you comply with the Require: clause; see below for details).

For operator->, it's typically implemented as simply return m_ptr; (or return get(); for unique_ptr). As you can see, the operator itself can't throw - it just returns the pointer. No dereferencing, no nothing. The language has some special rules for p->identifier:

§13.5.6 [over.ref] p1

An expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3).

The above applies recursively and in the end must yield a pointer, for which the built-in operator-> is used. This allows users of smart pointers and iterators to simply do smart->fun() without worrying about anything.

A note for the Require: parts of the specification: These denote preconditions. If you don't meet them, you're invoking UB.

Why then, is one of these specified as noexcept and the other not?

To be honest, I'm not sure. It would seem that dereferencing a pointer should always be noexcept, however, unique_ptr allows you to completely change what the internal pointer type is (through the deleter). Now, as the user, you can define entirely different semantics for operator* on your pointer type. Maybe it computes things on the fly? All that fun stuff, which may throw.


Looking at std::shared_ptr we have this:

This is easy to explain - shared_ptr doesn't support the above-mentioned customization to the pointer type, which means the built-in semantics always apply - and *p where p is T* simply doesn't throw.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...