带有shared_ptr vector的类的析构函数导致错误

Destructor of class with vector of shared_ptr causes error

本文关键字:析构函数 错误 带有 ptr vector shared      更新时间:2023-10-16

我有一个类Animal,它是几个不同动物的基类和一个类Herd,它将shared_prt存储到向量中的动物。我不熟悉智能指针,但是为了处理继承,我不得不在代码中使用它们。它似乎工作得很好,但在我的代码得到"Herd"的析构函数后,它抛出了一个错误。有什么问题吗?

class Animal {
public:
    Animal(string _sound) :
        sound(_sound) {}
    void give_sound() {
        cout << sound << " ";
    }
    bool operator==(Animal arg) {
        return (typeid(*this).name() == typeid(arg).name());
    }
protected:
    string sound;
};
class Dog : public Animal {
public:
    Dog() : Animal("woof") {}
};
class Cat : public Animal {
public:
    Cat() : Animal("meow") {}
};
class Cow : public Animal {
public:
    Cow() : Animal("moo") {}
};
class Herd {
public:
    Herd() {}
    ~Herd() {
        vec.clear();
    }
    Herd operator+(Animal *arg) {
        shared_ptr<Animal> ptr(arg);
        vec.push_back(ptr);
        return *this;
    }
    void operator+=(Animal *arg) {
        shared_ptr<Animal> ptr(arg);
        vec.push_back(ptr);
    }

    void make_noise() {
        vector<shared_ptr<Animal>>::iterator v = vec.begin();
        while (v != vec.end()) {
            (*v)->give_sound();
            v++;
        }
        cout << endl;
    }
private:
    vector<shared_ptr<Animal>> vec;
};
int main() {
    Herd herd;
    Dog d1, d2;
    Cat c1, c2;
    cout << "sound 1: " << endl;
    herd.make_noise();
    herd += &d1;
    herd += &c1;
    cout << "sound 2: " << endl;
    herd.make_noise();
    herd += &d2;
    herd += &c2;
    cout << "sound 3: " << endl;
    herd.make_noise();
    //herd = herd - &d1;
    //herd = herd - &d2;
    cout << "sound 4: " << endl;
    herd.make_noise();
    return 0;
}
如果没有vec.clear(),它也会崩溃。
Dog d1, d2;
Cat c1, c2;

这些对象具有自动保存时间。它们不应该被管理为拥有智能指针。

智能指针的用例是堆分配,例如:
herd += new Dog;

您的问题是传递具有自动存储持续时间的变量的地址。参见:c++中的堆栈、静态和堆

你的代码是这样写的:

创建一个具有自动存储持续时间的变量:

Dog d1

它将在超出作用域后自动销毁(在您的例子中是main函数的末尾)

然后,你把它的地址传递给一个函数,这个函数将这个地址存储在SharedPtr中:

Herd operator+(Animal *arg) {
    shared_ptr<Animal> ptr(arg);
    vec.push_back(ptr);
    return *this;
}

这样做是告诉shared_ptr它负责这个对象的删除。(简单来说,共享指针的析构函数将调用delete Animal)

结果你的对象将被释放两次,这是被禁止的。

不要使用原始指针,你应该使用:

operator+(shared_ptr<Animal> arg)

并按以下方式分配对象:

std::shared_ptr<Dog> d1 = std::make_shared<Dog>();

怎么了?

在这段代码中,你试图创建shared_ptr与堆栈分配的对象。这将导致该对象的双重删除,第一次删除发生在堆栈对象超出作用域时。第二次发生在delete算子的shared_ptr析构函数中。第二个无效,程序崩溃。

Herd operator+(Animal *arg) {
    shared_ptr<Animal> ptr(arg);
    vec.push_back(ptr);
    return *this;
}
void operator+=(Animal *arg) {
    shared_ptr<Animal> ptr(arg);
    vec.push_back(ptr);
}

我可以看出有两个明显的问题。

首先,正如其他人提到的,shared_ptr假设它管理的对象是动态创建的(使用操作符new),因此使用操作符delete释放它(除非在构造shared_ptr时提供了自定义删除器,而您的代码没有这样做)。对存储时间为auto的对象应用delete操作符会导致未定义的行为。

第二个问题——在解决第一个问题之后你最终会遇到的——是Animal类没有virtual析构函数。即使使用操作符new创建对象,操作符delete也会导致从Animal派生的实际类型对象的未定义行为(即,如果实际对象的类型为Cat, Dog等)。