Are the displaced objects still being destroyed?

Studying C ++ 11, I was surprised at how objects that behave move. Consider this code:

#include <exception> #include <iostream> #include <type_traits> class Moveable { public: Moveable() { std::cout << "Acquire odd resource\n"; } ~Moveable() noexcept(false) { std::cout << "Release odd resource\n"; // if (!std::uncaught_exception() && error_during_release) { // throw std::exception("error"); // } } Moveable(Moveable const &) = delete; Moveable &operator=(Moveable const &) = delete; Moveable(Moveable &&) = default; Moveable &operator=(Moveable &&) = default; }; int main(int argc, char *argv[]) { static_assert(!std::is_copy_constructible<Moveable>::value, "is not copy constructible"); static_assert(!std::is_copy_assignable<Moveable>::value, "is not copy assignable"); static_assert(std::is_move_constructible<Moveable>::value, "is move constructible"); static_assert(std::is_move_assignable<Moveable>::value, "is move assignable"); Moveable moveable{}; Moveable moved{std::move(moveable)}; Moveable moved_again{std::move(moved)}; } 

It outputs this result:

 $ clang++ --version clang version 3.8.0 (tags/RELEASE_380/final) Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /opt/clang+llvm-3.8.0-x86_64-linux-gnu-ubuntu-14.04/bin $ clang++ --std=c++14 --stdlib=libc++ -Wall -Werror -o move_and_destroy move_and_destroy.cc && ./move_and_destroy Acquire odd resource Release odd resource Release odd resource Release odd resource 

I am surprised because I was hoping to create a movable type of RAII . However, it seems that every moved intermediate object is destroyed!

Are there some options for this that allow me to release my resource once at the end of my “object lifetime”? (that is, at the end of the lifetime of a sequence of moving objects?)

Someone in a similar situation should probably use std::unique_ptr and do. However, in this case it is possible to throw ~Moveable() , and, apparently, the std::unique_ptr destructor will terminate the program by exception (at least in clang 3.8.0.)

+7
c ++ c ++ 11 move-semantics destructor raii
source share
5 answers

Yes, displaced objects are still being destroyed. To correctly release the resource once, after all the moves, we need to tell the destructor when the object was moved from:

 #include <exception> #include <iostream> #include <type_traits> class Moveable { private: bool moved_from; public: Moveable() : moved_from(false) { std::cout << "Acquire odd resource\n"; } ~Moveable() noexcept(false) { // We have already been moved from! Do nothing. if (moved_from) { std::cout << "Not releasing odd resource\n"; return; } std::cout << "Release odd resource\n"; // if (!std::uncaught_exception() && error_during_release) { // throw std::exception("error"); // } } Moveable(Moveable const &) = delete; Moveable &operator=(Moveable const &) = delete; Moveable(Moveable &&moveable) { moved_from = false; moveable.moved_from = true; // And now we spell out the explicit default move constructor } Moveable &operator=(Moveable &&moveable) { moved_from = false; moveable.moved_from = true; // And now we spell out the explicit default move assignment operator return *this; } }; int main(int argc, char *argv[]) { static_assert(!std::is_copy_constructible<Moveable>::value, "is not copy constructible"); static_assert(!std::is_copy_assignable<Moveable>::value, "is not copy assignable"); static_assert(std::is_move_constructible<Moveable>::value, "is move constructible"); static_assert(std::is_move_assignable<Moveable>::value, "is move assignable"); Moveable moveable{}; Moveable moved{std::move(moveable)}; Moveable moved_again{std::move(moved)}; } 

This gives

 $ clang++ --std=c++14 --stdlib=libc++ -Wall -Werror -o move_and_destroy move_and_destroy.cc && ./move_and_destroy Acquire odd resource Release odd resource Not releasing odd resource Not releasing odd resource 
0
source share

Yes, displaced objects are destroyed. They remain in an uncertain but valid state. They are still objects.

Best of all, if you remember that C ++ doesn't actually move anything. std::move just gives you the rvalue value. The so-called "move constructors" are simply convenient alternatives for copying constructors found during a search when you have an rvalue value, and allowing you to exchange encapsulated class data, rather than copying them. But C ++ does not move anything for you, and it cannot say when you did some kind of move.

Thus, it would be impracticably dangerous and impractical if in C ++ there was some rule that somehow stopped the “moved” objects, if we could even decide what it means in general, from a later destruction . Make it safe to destroy (no-op, perfect) for your moved objects (for example, setting the source pointers to nullptr in your move constructor) and everything will be fine.

+7
source share

"Are there any options for this that allow me to release my resource once at the end of my" lifetime of an object "? (That is, at the end of a lifetime of a sequence of moved objects?)"

This is what is about to happen. Do not forget that by the time the object was destroyed from the object, your move constructor deleted its resources, so it has nothing to free up. It remains to free only the final object moved to the object.

0
source share

By changing your code a bit and introducing a couple of tracking variables, we can see what is happening more clearly, and I also demonstrate my intention to move by adding the resource that we are “moving”:

 #include <exception> #include <iostream> #include <type_traits> class Moveable { static int s_count; int m_id; const char* m_ptr; public: Moveable(const char* ptr) : m_id(s_count++), m_ptr(ptr) { std::cout << "Moveable(ptr) " << m_id << '\n'; } Moveable(Moveable&& rhs) : m_id(s_count++), m_ptr(nullptr) { std::cout << "Moveable(&&) " << m_id << " from " << rhs.m_id << '\n'; std::swap(m_ptr, rhs.m_ptr); } ~Moveable() noexcept(false) { std::cout << "Release " << m_id << " m_ptr " << (void*)m_ptr << '\n'; } Moveable(Moveable const &) = delete; Moveable &operator=(Moveable const &) = delete; }; int Moveable::s_count; int main(int argc, char *argv[]) { Moveable moveable{"hello world"}; Moveable moved{std::move(moveable)}; Moveable moved_again{std::move(moved)}; } 

Output :

 Moveable(ptr) 0 Moveable(&&) 1 from 0 Moveable(&&) 2 from 1 Release 2 m_ptr 0x8048a26 Release 1 m_ptr 0 Release 0 m_ptr 0 

As expected, the original object is destroyed last. Our constructor move , tho, passed the resource that he was tracking so that ultimately it was tracked # 2, and not # 0 - objects # 0 and # 1 are empty; if we used std::unique_ptr<> or something to own a resource, only one of the objects would try to delete it.

Note that this was the move constructor, not the call to std::move that caused this transfer.

0
source share

Consider this as the base class for your resources:

 class Resource { private: mutable bool m_mine; protected: Resource() : m_mine( true ) { } Resource(const Resource&) = delete; void operator=(const Resource&) = delete; Resource(const Resource&& other) : m_mine( other.m_mine ) { other.m_mine = false; } bool isMine() const { return m_mine; } }; 

Then you just check isMine() in your destructor and release / release if true. This allows you to create const fields.

If this is a valid script to close and reopen the same resource, consider using std::optional<MyResource> and free features that take this type for operations that are also valid for a closed stream (e.g. reopen). If you don't like the free features, you can put them in a static helper class.

0
source share

All Articles