While learning C++11, I was surprised by the way moved objects appear to behave. 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 yields this output:
$ 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'm surprised because I was hoping to create a movable RAII type. However it seems each moved intermediate is destructed!
Is there some variation of this that allows me to release my resource once at the end of my "object's lifetime"? (that is, at the end of the lifetimes of the sequence of moved objects?)
Someone in a similar situation should probably use std::unique_ptr
and be done. However in this scenario it is possible for ~Moveable()
to throw, and apparently std::unique_ptr
's destructor will terminate the program on exception (at least, in clang 3.8.0.)
- The Old New Thing What should the state of a moved-from object be? In the C++ language, there is the concept of moving, which is a way of transferring resources from one object to another. The language specifies that a moved-from object is in a legal but indeterminate state.
The language specifies that a moved-from object is in a legal but indeterminate state. Basically, the object is in a state that can be safely destructed, or operated upon in a way that is not dependent on the previous state (say, assigning a new value).
You might be a victim of disappearing object phenomena (DOP). What could be the cause? Typically, DOP involves an object that the person had just been using or that they invariably keep in one particular place.
This is because the object is now stack based object and it is destroyed when it is goes out of scope and its destructor will be called. How about global static objects? The following program demonstrates use of global static object.
Yes, moved-from objects are destructed. They remain in an undetermined but valid state. They're still objects.
It's best if you recall that C++ doesn't actually move anything. std::move
just gives you an rvalue. So-called "move constructors" are just convenient alternatives to copy constructors, found during lookup when you have an rvalue, and allowing you the opportunity to swap your class's encapsulated data rather than actually copying it. But C++ doesn't move anything for you, and it can't tell when you have done some moving.
As such, it would be infeasibly dangerous and impractical for C++ to have any kind of rule that somehow stopped "moved-from" objects, if we could even decide what this meant in general, from later undergoing destruction. Make this destruction safe (a no-op, ideally) for your moved-from objects (e.g. by setting source pointers to nullptr
in your move constructor) and you'll be fine.
Yes, moved-from objects are still destructed. To correctly release the resource once, after all moves, we need to tell the destructor when the object has been 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 yields
$ 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With