被移动的物体仍被破坏?

时间:2016-07-10 00:10:48

标签: c++ c++11 destructor move-semantics raii

在学习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中。)

5 个答案:

答案 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)};
}

The 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

正如我们所料,原始对象最后被销毁。我们的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>并将此类型的自由函数用于在封闭流上有效的操作(例如,重新打开)。如果你不喜欢自由函数,你可以将它们放在一个静态帮助器类中。