【问题标题】:Destructor of class with vector of shared_ptr causes error具有 shared_ptr 向量的类的析构函数导致错误
【发布时间】:2016-11-16 10:30:28
【问题描述】:

我有一个 Animal 类,它是几个不同 Animals 的基类,还有一个 Herd 类,它将 shared_prt 存储在一个向量中的动物身上。我不熟悉智能指针,但我不得不在我的代码中使用它们来处理继承。它似乎工作正常,但在我的代码到达“Herd”的析构函数后,它会抛出一个error。 它有什么问题?

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() 它也会崩溃。

【问题讨论】:

  • 发布问题中的代码。
  • @molbdnilo 在这里
  • "我不熟悉智能指针,但我不得不在我的代码中使用它们来处理继承问题。" 我有点困惑。智能指针显然在这里不起作用,因为对象是在堆栈上分配的。但是一个普通的、愚蠢的指针应该工作得很好。为什么你认为在这里需要智能指针?

标签: c++ oop vector smart-pointers


【解决方案1】:
Dog d1, d2;
Cat c1, c2;

这些对象具有自动存储期限。它们不应该被拥有智能指针来管理。

智能指针的用例是堆分配,例如:

herd += new Dog;

【讨论】:

  • 那我应该如何申报呢?
  • @IFeel3,你不需要。 std::make_shared 创建一个具有动态存储持续时间的对象,并立即将其交给智能指针。你不需要声明d1, d2, c1, c2
  • 不喜欢像Dog *d = new Dog;这样声明Animal对象吗?
  • @IFeel3,这将创建一个具有动态存储持续时间的对象,是的。但不是首选。如果您的Herd 拥有其中的动物,请直接交出。不要将指向它们的指针保留在容器之外(您可能会不小心在它们上调用delete)。请注意,我更新了答案以适合您的实际代码。
【解决方案2】:

您的问题是使用自动存储持续时间传递变量的地址。 见:Stack, Static, and Heap in C++

这就是您的代码中发生的情况:

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

Dog d1

它会在超出范围后自动销毁(在你的情况下,主函数结束)

然后,您将其地址传递给将该地址存储在 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>();

【讨论】:

    【解决方案3】:

    这有什么问题?

    在此代码中,您尝试使用堆栈分配的对象创建shared_ptr。这会导致该对象的双重删除,第一次删除发生在堆栈对象超出范围时。第二个发生在shared_ptr 析构函数中的delete 运算符中。第二个无效,程序崩溃。

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

    【讨论】:

    • 更准确地说,错误发生在 first 删除时。当标准全局分配器无法找到其元数据时。第一次析构函数调用不是由于 delete 而是自动的。
    • 是的,错误发生在delete操作符第一次删除堆栈分配对象时。
    【解决方案4】:

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

    第一个,正如其他人所提到的,shared_ptr 假定它管理的对象是动态创建的(使用运算符 new)所以使用运算符 delete 释放它(除非在构造 @ 时提供了自定义删除器987654324@,您的代码没有这样做。将 operator delete 应用于具有 auto 存储持续时间的对象会导致未定义的行为。

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

    【讨论】:

      猜你喜欢
      • 2018-09-24
      • 2012-03-09
      • 2020-09-28
      • 1970-01-01
      • 2018-07-03
      • 2013-04-30
      • 1970-01-01
      • 2015-09-22
      • 2020-05-28
      相关资源
      最近更新 更多