来自std :: vector的std :: remove_if polymorphic std :: unique_ptr

Dei*_*Dei 9 c++ polymorphism c++14

我有三个类的层次结构,Derived派生自SelectableDrawable.然后,我有一个std::vectorstd::unique_ptr<Drawable>,我充满了Derived对象.

确信矢量将仅由同时来自两个碱基的物体填充.

当我尝试通过使用指针从向量中删除某个元素时,问题就出现了Selected.

#include <vector>
#include <memory>
#include <algorithm>

struct Selectable {
    virtual ~Selectable() = 0;
};
Selectable::~Selectable() = default;

struct Drawable {
    virtual ~Drawable() = 0;
};
Drawable::~Drawable() = default;

struct Derived : Selectable, Drawable {};

int main()
{
    std::vector<std::unique_ptr<Drawable>> vec;
    for (int i = 0; i < 5; ++i) {
        vec.push_back(std::make_unique<Derived>());
    }
    Selectable* selected = dynamic_cast<Selectable*>(vec[2].get());

    vec.erase(std::remove_if(vec.begin(), vec.end(), 
        [selected](auto&& ptr) { 
            return ptr.get() == dynamic_cast<Drawable*>(selected); 
    }), vec.end());
}
Run Code Online (Sandbox Code Playgroud)

显然,如果我selected成为一个指针Drawable,一切都很好,但这不是我的意图.

我收到一个运行时错误,导致程序崩溃.为什么会发生这种情况,我该如何解决?

Dav*_*aim 10

关键问题在于std::remove_if"移除"元素的方式:

通过移动(通过移动分配)范围中的元素来完成移除,使得不被移除的元素出现在范围的开头.保留的元素的相对顺序被保留,容器的物理大小不变.指向新逻辑端和范围的物理端之间的元素的迭代器仍然是可解除引用的,但元素本身具有未指定的值 (根据MoveAssignable后置条件).

所以基本上,你保留一个原始指针auto ptr = vec[2].get(),但没有一个保证ptr仍然有效.您只能保证vec[2]有效.(vec[2]过滤之前的唯一指针现在位于新逻辑端和物理端之间,未指定值).

在您的示例中,当std::remove_if到达第三个元素时,谓词返回trueremove_if调用vec[2].get()析构函数.因为你保持一个原始指针,你正在使用指向已被销毁的对象的指针.

  • @DeiDei使用`std :: find` +`std :: vector :: erase` (3认同)

Ser*_*kov 5

程序崩溃的原因是你调用dynamic_cast了无效指针.只需向析构函数添加输出并选择打印即可轻松演示:

struct Selectable {
    virtual ~Selectable();
};
Selectable::~Selectable() {
  std::cout << "Selectable::~Selectable:" << this << std::endl;
};

struct Drawable {
    virtual ~Drawable();
};
Drawable::~Drawable() {
  std::cout << "Drawable::~Drawable:" << this << std::endl;
}

vec.erase(std::remove_if(vec.begin(), vec.end(), 
    [selected](auto&& ptr) { 
        std::cout << "selected:" << selected << std::endl;
        return ptr.get() == dynamic_cast<Drawable*>(selected); 
}), vec.end());
Run Code Online (Sandbox Code Playgroud)

这是一个可能的输出:

$ ./a.exe
selected:0x3e3ff8
selected:0x3e3ff8
selected:0x3e3ff8
selected:0x3e3ff8
Drawable::~Drawable:0x3e3ffc
Selectable::~Selectable:0x3e3ff8
selected:0x3e3ff8
Segmentation fault
Run Code Online (Sandbox Code Playgroud)

调用dynamic_cast无效指针是未定义的行为.

显然,如果我selected成为一个指针Drawable,一切都很好,但这不是我的意图.

在这种情况下,您也有一个无效的指针,但dynamic_cast由于它不是必需的,因此不会由编译器生成.在这种情况下,您的程序可以避免崩溃.