【问题标题】:Why is the desctructor being invoked in this scenario?为什么在这种情况下会调用析构函数?
【发布时间】:2022-10-15 18:41:27
【问题描述】:

在下面的代码中,我无法理解为什么类 Buf 的析构函数被调用了两次。调试时,我可以看到它在运行线程离开函数Test::produce 时第一次被调用。第二次是离开主要功能时,本质上是在破坏类EventQueue时,这是我所期望的。

但是,我不明白为什么在离开函数Test::produce 时会调用Buf 的析构函数。具体来说,我创建类Buf 作为右值,将其传递给EventQueue 并移至其内部缓存。事实上,这给我带来了一个问题,即我最终尝试两次释放相同的指针,这会引发异常。

template<typename T>
class EventQueue{
public:
    void offer(T&& t) {
        m_queue.try_emplace(std::this_thread::get_id()).first->second.push(std::move(t));
    };

    std::unordered_map<std::thread::id, std::queue<T>> m_queue;

};

class Buf{
    const uint8_t *m_data;
    const size_t m_size;
public:
    Buf(const uint8_t *data, size_t size) : m_data(data), m_size(size) { }
    size_t size() const { return m_size; }
    const uint8_t *data() const { return m_data; }
    ~Buf()
    {
        std::cout << "dtor called " << std::endl;
        free((void *)m_data);
    }
};


class Test{ and was not expecting
public:
    Test(shared_ptr<EventQueue<Buf>> buf) : m_buf(buf)
    {
        std::thread t1 = std::thread([this] { this->produce(10); });
        t1.detach();
    };

    void produce(int msg_size) {
        m_buf->offer(Buf(new uint8_t[msg_size], 10));
    }
    std::shared_ptr<EventQueue<Buf>> m_buf;
};

int main()
{
    auto event_queue = std::make_shared<EventQueue<Buf>>();
    Test tt(event_queue);

    return 0;
}

【问题讨论】:

  • 当您将值从一个对象移动到另一个对象时,原始对象仍然存在,但处于已移出状态。无论是否移出,它都会在其生命周期结束时被销毁。移动对象不会也不会导致其析构函数被跳过。
  • m_buf-&gt;offer(Buf(new uint8_t[msg_size], 10)); 表示一个临时的 Buf 对象,该对象被移动到内存位置 EventQueue&lt;Buf&gt;::m_queue 需要它去...这就是为什么你应该总是删除默认的复制/移动构造函数/赋值运算符,通过定义移动构造函数/赋值运算符作为已删除,除非您知道按成员移动可以解决问题,或者您自己实现复制和/或移动语义(或者已经有一个成员导致隐式定义的特殊成员被删除)...
  • 您的 Buf 类包含严重的错误,因为它不遵循 0/5/3 的规则:stackoverflow.com/questions/4172722/what-is-the-rule-of-three
  • 我看到new,但没有看到delete。我确实看到了free,但情况可能更糟。 new[]delete[] 配对。没有什么可以指望完成这项工作。我们可以说服您使用std::vector 吗?
  • std::cout &lt;&lt; "dtor called " &lt;&lt; std::endl; -- 您应该打印this 的值,而不仅仅是一条简单的消息,即std::cout &lt;&lt; "dtor called on object " &lt;&lt; this &lt;&lt; std::endl; 您很可能没有删除您认为正在删除的同一个对象。这不是析构函数被调用两次的问题——析构函数被调用一次,而是你正在处理不同的对象。

标签: c++


【解决方案1】:

析构函数被调用两次,因为你有对象两次销毁。第一的- 您为 offer 函数参数创建的临时参数:

void produce(int msg_size) {
    m_buf->offer(Buf(new uint8_t[msg_size], 10));
}

第二- 当您将此临时添加到std::queue 容器时,它会在后台创建一个副本:

void offer(T&& t) {
    m_queue.try_emplace(std::this_thread::get_id()).first->second.push(std::move(t));
};

创建的每个临时对象也必须始终被销毁。然而,问题不在于销毁了多少对象,而在于您在这里忽略了零、三和五的规则。 IE。如果您创建任何析构函数、复制构造函数或复制赋值运算符,首先,您应该处理所有三个和第二个 - 编译器将不是为您生成移动构造函数和移动赋值运算符。但即使这样做了,它也不能解决您的问题,因为您的类实例“拥有”(并且应该在某些时候删除)的原始指针与隐式移动构造函数 which merely does member-wise std::move 不太兼容:

对于非联合类类型(classstruct),移动构造函数使用带有 xvalue 参数的直接初始化,按照初始化顺序对对象的基类和非静态成员执行完整的成员移动。

对于任何内置类型(包括原始指针),这意味着实际上什么都没有发生,它们只是被复制了。

长话短说:您必须明确地取消源对象的成员原始指针:

Buf(Buf&& other) noexcept : m_data{ std::exchange(other.m_data, nullptr) }, m_size{ other.m_size } {}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-20
    相关资源
    最近更新 更多