Why is observ_ptr not reset after moving?

Why is there no observer_ptr after the move operation?

It is correctly set to nullptr in its default construct, and this makes sense (and does not allow garbage to be pointed out).

And, accordingly, it should be reset to zero when std::move() 'd from, like std::string , std::vector , etc.

This would make him a good candidate in several contexts where the initial pointers make sense, as well as the automatic generation of operations for moving around classes with data elements with raw pointers, for example, in this case .


EDIT

As @JonathanWakely noted in the comments (and this is related to the above question ):

if observer_ptr was null after the move, it can be used to implement the null rule for types that contain a pointer. This is a very useful feature.

+6
source share
3 answers

It seems that many people overlook the essence and usefulness of this idea.

Consider:

 template<typename Mutex> class unique_lock { Mutex* pm; public: unique_lock() : pm() { } unique_lock(Mutex& m) : pm(&m) { } ~unique_lock() { if (pm) pm->unlock(); } unique_lock(unique_lock&& ul) : pm(ul.pm) { ul.pm = nullptr; } unique_lock& operator=(unique_lock&& ul) { unique_lock(std::move(ul)).swap(*this); return *this; } void swap(unique_lock& ul) { std::swap(pm, ul.pm); } }; 

With the help of a "dumb" smart pointer, which is null-on-default-construction and null-after-move, you can use three special member functions by default, so it becomes as follows:

 template<typename Mutex> class unique_lock { tidy_ptr<Mutex> pm; public: unique_lock() = default; // 1 unique_lock(Mutex& m) : pm(&m) { } ~unique_lock() { if (pm) pm->unlock(); } unique_lock(unique_lock&& ul) = default; // 2 unique_lock& operator=(unique_lock&& ul) = default; // 3 void swap(unique_lock& ul) { std::swap(pm, ul.pm); } }; 

This is why it is useful to have a dumb non-owning smart pointer that is null after moving, for example tidy_ptr

But observer_ptr is null-on-default-construction, so if it is standardized, it will be useful to declare that the function accepts a pointer without permissions, but it will not be useful for classes like the one above, so I still need a different type of mute pointer without rights. Having two non-owning dumb smart types of pointers seems almost worse than anyone!

+10
source

Thus, move constructors are designed to make copy constructors cheaper in some cases.

Write down what we expect from these constructors and destructors: (This is a bit of a simplification, but it's good for this example).

 observer_ptr() { this->ptr == nullptr; } observer_ptr(T *obj) { this->ptr = obj; } observer_ptr(observer_ptr<T> const & obj) { this->ptr = obj.ptr; } ~observer_ptr() { } 

You assume that the class provides a move constructor that looks like this:

 observer_ptr(observer_ptr<T> && obj) { this->ptr = obj.ptr; obj.ptr = null; } 

When I would assume that the existing copy constructor would work just as it is, and cheaper than the proposed move constructor.

How about std::vector though?

A std :: vector when copying actually copies the array that it supports. Thus, the copy constructor std :: vector looks something like this:

 vector(vector<T> const & obj) { for (auto const & elem : obj) this->push_back(elem); } 

The move constructor for std :: vector can optimize this. He can do this because this memory can be stolen from obj . In std :: vector, this is really useful to do.

 vector(vector<T> && obj) { this->data_ptr = obj.data_ptr; obj.data_ptr = nullptr; obj.size = 0; } 
+2
source

In addition to the slight performance impact on zeroing the moved from observer_ptr , which is easy to process (copy instead of moving), the main rationale was probably to simulate the behavior of regular pointers as closely as possible, following the principle of least surprise .

However, there is a much more significant performance problem: by default, the move functions allow observer_ptr be trivially copied . The trivial ability to copy allows you to copy an object using std::memcpy . If observer_ptr were not trivially copied, there would be no classes with an observer_ptr data member, which would result in a performance degradation that cascades the composition class hierarchy.

I do not know what performance improvements can be obtained using the optimization of std::memcpy , but they are probably more significant than the aforementioned minor performance problem.

0
source

All Articles