【问题标题】:Destruction of a non-existing object销毁不存在的对象
【发布时间】:2023-04-08 02:23:01
【问题描述】:

我在移动语义方面有点挣扎。我阅读了很多关于该主题的内容,但是,有两个具体问题我没有找到任何答案,因此想向您介绍它们。

首先,我有以下示例代码:

#include <iostream>
#include <cstddef>

class A
{
    public:
        A *ptr;
        virtual ~A() 
        { 
            std::cout << "dtor" << std::endl; 
            delete ptr; 
            ptr=nullptr;
        }
};

main()
{
    A x, y;
    x.ptr = &y;
}
// compile and link like this with g++: g++ -std=c++0x -lstdc++ code.cc

A 类可以拥有自己的成员。我使用这种“复合模式”概念来构建层次结构。但是,在这种情况下,当对象 x 被析构时,其析构函数会删除指向对象 y 的指针。当最终对象 y 应该被破坏时,它已经产生了错误......我该如何解决这个问题?

第二种情况如下:

main()
{
    A x, y;
    y = std::move(x);
    std::cout << "x.ptr: " << x.ptr << std::endl;
    std::cout << "y.ptr: " << y.ptr << std::endl;
}

在这里,我可以看到两个引用是相等的,这意味着两者都存在。我虽然 std::move 将内容从 x 移动到 y。我原以为 x.ptr 是“空的”....?

提前致谢!

干杯

【问题讨论】:

  • 你有一个delete和零个news。
  • 测试时我得到了 x.ptr 和 y.ptr 作为空值。第二个我也没有收到任何错误。
  • 解决第二种情况:std::move 本身并没有做太多事情,数据的 移动 由移动构造函数完成,默认移动构造函数是其中的成员- 明智的移动,但对于原语等(如指针)只是一个副本。因此,它不会将x.ptr 设置为“空”
  • 在你的析构函数末尾做ptr=nullptr;没有意义的
  • 解决第一种情况:这就是为什么原始指针A* 是表示所有权的不好方式的原因。 unique_ptrshared_ptr 在“拥有”另一个对象时是首选,因为它们在语义上都很清晰,并且有明确定义的清理方式。

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


【解决方案1】:

在第一种情况下,您的 x 和 y 对象都是在堆栈上创建的,因此它们将被自动销毁。但是,您的 A 类的析构函数使用 delete,它假定 ptr 引用的对象是在使用 new 的堆上创建的。 因此,将 x.ptr 设置为引用在堆栈上创建的对象(如 y)是错误的。正确的代码是

x.ptr = new A;

这是原始指针的危险 - 您无法区分指向动态分配对象的指针(您最终必须 delete)和“只是指针”(您不能)。

最好使用std::unique_ptr&lt;A&gt; 而不是原始指针A*。 它还会调用 delete my 本身,因此不需要您的 delete ptr

至于第二种情况,对于原始类型,移动实际上只是一个副本。如果您从 int 移动到 int,或从 ptr 到 ptr,它只是一个副本,因为对于原始类型没有什么可以优化的移动,移动对象没有特殊的“清空”。但是对于您的情况,有一些称为智能指针的好东西实际上会“清空”(因为它保证了正确性)。你猜对了——我说的是std::unique_ptr

同样,如果您将 ptr 从 A* 更改为 std::unique_ptr&lt;A&gt;,那么 move 将完成您正在寻找的工作。

简而言之,std::unique_ptr 是您真正的朋友。

【讨论】:

    【解决方案2】:

    首先,您在堆栈上创建两个 A 类型的对象

    A x,y;
    

    然后将y的地址分配给成员x.ptr。当对象超出范围时,将调用它们的析构函数。这与它们的构造顺序相反,所以它应该是

    y.~A()
    x.~A()
    

    在您的情况下,您在析构函数中手动调用 delete,这将在执行 x.~A() 时对已删除的对象调用 delete。

    这不是代码中唯一的问题:

    • You call delete on an object on the stack.deletes 的调用应与对new 的调用相匹配。更好的是,您应该手动调用 newdelete 并使用 std::unique_ptr 和 std::shared_ptr`。
    • b 的析构函数被调用时,它会delete ptr 使用未初始化的ptr 值。编译器生成的构造函数不会初始化 ptr tp nullptr。我知道至少有一个编译器在调试模式下但在发布模式下执行此操作。

    当你使用std::move 时,它最终会调用move 构造函数(或move assignmnet 运算符)。由于您没有手动定义一个,而是定义了一个析构函数,因此不会有编译器生成的移动构造函数。因此,在您的情况下,它最终将调用隐式复制构造函数并复制成员值,这就是您所看到的。

    就算加了

    class A
    {
        public:
            A(A&& a) = default;
            A& operator=(A&& a) = default;
    
            A *ptr;
            virtual ~A() 
            { 
                std::cout << "dtor" << std::endl; 
                delete ptr; 
                ptr=nullptr;
            }
    };
    

    要请求编译器生成的移动语义,它将生成does a member-wise move 的代码,看起来像

    A(A&& a): ptr( std::move(a.ptr) ) {}
    

    对于指针类型,这只是将值分配给新变量并保持旧变量不变。标准对移动对象的要求是非常基本的,并由此满足。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 2017-01-11
      • 2013-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多