尝试将unique_ptrs推送到向量时使用纯虚拟函数错误

Getting use of pure virtual function error when trying to push unique_ptrs to a vector

本文关键字:虚拟 错误 函数 向量 unique ptrs      更新时间:2023-10-16

所以我有一个抽象类,名为MyClassParent,MyClass从中继承。我运行以下代码:

for(auto e:elements){
MyClass m = *this;
MyClass * mpointer = &m;
if(mpointer->xLargerthanY(x,y)){
rv.push_back(unique_ptr<MyClassParent>(mpointer));
if(!rv[0]->solved()) cout<<"true";//works as expected
}
}
rv[0]->solved();//gives pure virtual function called error

奇怪的是,for each循环内部的rv[0]->solved()按预期工作,如果对象的x大于y,则返回true。但如果我从for each环路外部调用函数,我会得到一个名为error的纯虚拟函数,这应该永远不会发生,因为我在子类中重写了solved)。我怀疑这与unique_ptr函数有关,因为我求解的方法不对对象进行任何更改,只返回true或false。

我已经用许多其他方法测试了这一点,它们都在for每个循环中工作,但一旦我退出它,我就会得到一个名为error的纯虚拟函数。

rv[0]->solved();//gives pure virtual function called error

当然。你的程序有未定义的行为,所以它可以做任何事情。将该片段提取为导致问题的原因也相当容易:

MyClass *ptr;
{
MyClass m;
ptr = &m;
}
ptr->solved();

一旦我们消除了所有这些危险,我们就会看到rv容器中的所有指针都指向具有自动存储持续时间的对象,这些对象已经超出了范围。使用它们访问该对象只会以某种未定义的方式进行操作。

如果您想让rv存储拥有指向this副本的指针,那么创建那些具有动态存储持续时间的副本

for(auto e:elements){
MyClass& m = *this; // Assuming we need the reference binding
if(m.xLargerthanY(x,y)){
rv.push_back(make_unique<MyClass>(m)); 
}
}

现在所有的东西都指向有效的对象。

好的,让我们从一点介绍开始,因为您似乎没有掌握理解智能指针所需的一些东西:

自动存储持续时间:对象的生存期由编译器管理。其生存期由关联变量的范围定义。

例如:

{
X x; // lifetime of x starts here
// ....
} // lifetime of x ends here

动态存储持续时间:对象的生存期由程序员管理。它从对new的调用开始,并以对delete的调用结束(这被简化了一点)。

例如:

auto foo(X* x)
{
delete x; // lifetime ends with delete
}

{
X* x = new X{}; // lifetime starts with new
foo(x);
}

在C++中,永远不应该显式调用new/delete,而应该使用智能指针。

销毁时的unique_ptr(除非另有指定)将自动调用其保留的指针上的delete。这就是必须为其提供一个指向动态对象的指针的原因,即分配有new。这是你的问题之一。

X x;
std::unique_ptr<X> p{&x};
// p receives a pointer to an automatic storage duration
// this is 100% wrong. The destructor for x would be called twice
// once as part of the automatic lifetime of x
// and then as part of the destructor of p
// resulting in UB

这就是你在这里所做的:

MyClass m = ...;
MyClass * mpointer = &m;
unique_ptr<MyClassParent>(mpointer);
// unique_ptr receives a pointer to an automatic storage duration object

似乎这还不够,您遇到的另一个问题是访问悬挂指针

m的范围在的范围内。向量包含指向这些对象的指针,并且这些对象在每次迭代后都会超出范围。执行rv[0]时,访问的对象的生存期已结束。再次出现未定义的行为。

我希望你能更好地了解unique_ptr的作用以及它解决了什么问题。正如Storry Teller所展示的那样,解决方案是使用make_unique

make_unique的作用:它调用new,并根据new返回的指针创建一个unique_ptr。你可以自己动手做,但不应该这样做,因为还有其他问题:std::make_unique和std::unique_ptr 之间的差异

正如@StoryTeller所指出的,这是未定义的行为,但让我解释一下为什么它在这种情况下会这样做。由于它是未定义的行为,因此不能保证它在不同的编译器或系统中会以这种方式运行,但我将解释为什么它很有可能:

for(auto e:elements){
MyClass m = *this;
MyClass * mpointer = &m;
if(mpointer->xLargerthanY(x,y)){
rv.push_back(unique_ptr<MyClassParent>(mpointer));
if(!rv[0]->solved()) cout<<"true";//works as expected
}
}
rv[0]->solved();//gives pure virtual function called error

此处

for(auto e:elements){
MyClass m = *this;
....
}

指向CCD_ 20的指针被存储到CCD_。但当m存在作用域时,对象正在被破坏。该代码隐式调用MyClass::~MyClass(),它最终替换了对象的虚拟表。首先,派生类被销毁,在销毁的最后一步,虚拟表被替换,这样对象就没有基的虚拟表。在基地,solved()是纯虚拟的,所以调用:

rv[0]->solved();

因此,调用此函数只能找到基的定义。派生对象已不存在,因此无法使用。在这种情况下,在基类中,resolved()是纯虚拟的,并且没有实体。这就是它崩溃的原因。如果你的基础中有一个非虚拟的resolved(),那么你很有可能会发生不同的崩溃,因为对象已经被破坏了。

请注意,即使此代码没有崩溃,稍后当rv被销毁时,事情也会变得一团糟。rv内部的指针指向堆栈,但std::unique_ptr调用delete并假定对象在堆上。要么它会立即崩溃,因为这是一个非法指针,堆/堆栈将被丢弃,要么它将被简单地忽略。确切的行为是未知的,因为这也是未定义的行为。

当你遇到类似的问题时,这里有一个更简单的例子:

class Base
{
public:
virtual ~Base() { bar(); }
virtual void foo() = 0;
void bar() { foo(); }
};
class Derived: public Base
{
public:
void foo() override { };
};
int main()
{
Derived b;
}