在学习C ++ 11时,我对移动对象的行为方式感到惊讶。请考虑以下代码:
#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)};
}
它产生了这个输出:
$ 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
我很惊讶,因为我希望创建一个可移动的RAII类型。然而,似乎每个移动的中间体都被破坏了!
是否有一些变体允许我在对象的生命周期结束时释放我的资源&#34;? (也就是说,在移动物体序列的生命周期结束时?)
处于类似情况的人应该使用std::unique_ptr
并完成。但是在这种情况下,~Moveable()
可能会抛出,显然std::unique_ptr
的析构函数将在异常时终止程序(至少在clang 3.8.0中。)
答案 0 :(得分:8)
是的,移动的对象被破坏了。他们仍处于未确定但有效的状态。他们仍然是对象。
如果您记得C ++实际上并没有移动任何东西,那将是最好的。 nullptr
只给你一个右值。所谓的&#34;移动构造函数&#34;只是方便的复制构造函数的替代方法,在查找时有rvalue,并且允许你有机会交换类的封装数据而不是实际复制它。但是C ++并没有为你移动任何东西,它也无法告诉你何时做了一些移动。
因此,对于C ++而言,任何类型的规则都会以某种方式停止并从#34;移动到C#,这是不可能的危险和不切实际的。对象,如果我们甚至可以决定这通常意味着什么,从后来遭受破坏。对于移动的对象(例如,通过在移动构造函数中将源指针设置为#include <stdio.h>
int main(void)
{
char c;
int charSize = sizeof(c); // Here it is an int
printf("char: %lu\n", sizeof(c)); // Here it is an unsigned long
printf("char: %i\n", charSize);
}
),使这种破坏安全(理想情况下是无操作),你就可以了。
答案 1 :(得分:0)
“是否有一些变体允许我在”对象的生命周期“结束时释放我的资源?(即,在移动对象序列的生命周期结束时?)”
这是应该发生的事情。不要忘记,当从对象移动时,移动构造函数已删除其资源,因此没有任何内容可以解除分配。只有最后的移动到对象才会保留要释放的资源。
答案 2 :(得分:0)
略微修改您的代码并引入一些跟踪变量,我们可以更清楚地看到发生的事情,并且我还通过添加我们移动的资源来展示移动的意图&#34;:
#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)};
}
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
正如我们所料,原始对象最后被销毁。我们的move
构造函数转移了它正在跟踪的资源,以便它最终被#2跟踪而不是#0 - 对象#0和#1是空的;如果我们使用std::unique_ptr<>
或其他东西拥有资源,则只有其中一个对象会尝试删除它。
请注意,导致此传输的是移动构造函数而不是std::move
调用。
答案 3 :(得分:0)
是的,移动的对象仍然被破坏。要在所有移动之后正确释放资源一次,我们需要告诉析构函数何时移动对象:
#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)};
}
这会产生
$ 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
答案 4 :(得分:0)
将此视为资源的基类:
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;
}
};
然后你只需检查你的析构函数中的isMine()
并解除分配/释放,如果是的话。这允许使用const字段。
如果它是关闭并重新打开相同资源的有效方案,请考虑使用std::optional<MyResource>
并将此类型的自由函数用于在封闭流上有效的操作(例如,重新打开)。如果你不喜欢自由函数,你可以将它们放在一个静态帮助器类中。