繁体   English   中英

std :: remove_if来自std :: vector的多态std :: unique_ptr

[英]std::remove_if polymorphic std::unique_ptr from std::vector

我有三个类的层次结构,其中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());
}

显然,如果我selected作为Drawable的指针,那么一切都很好,但这不是我的意图。

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

关键问题在于std::remove_if “删除”元素的方式:

删除是通过移动(通过移动分配)范围中的元素来完成的,要删除的元素出现在范围的开头。 保留了剩余元素的相对顺序,并且容器的物理大小未更改。 指向范围的新逻辑端和物理端之间的元素的迭代器仍然是可取消引用的, 但是元素本身具有未指定的值 (根据MoveAssignable后置条件)。

因此,基本上,您保留了由auto ptr = vec[2].get()获取的原始指针,但是没有人保证ptr保持有效。 您只能保证vec[2]有效。 (以前在过滤之前位于vec[2]的唯一指针现在位于新逻辑端和物理端之间,且未指定值)。

在您的示例中,当std::remove_if到达第三个元素时,谓词返回trueremove_if调用vec[2].get()的析构函数。 由于保留了原始指针,因此您使用的是指向已被破坏的对象的指针。

程序崩溃的原因是您为无效的指针调用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());

这是可能的输出:

$ ./a.exe
selected:0x3e3ff8
selected:0x3e3ff8
selected:0x3e3ff8
selected:0x3e3ff8
Drawable::~Drawable:0x3e3ffc
Selectable::~Selectable:0x3e3ff8
selected:0x3e3ff8
Segmentation fault

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

显然,如果我selected作为Drawable的指针,那么一切都很好,但这不是我的意图。

在这种情况下,你也有一个无效的指针,但dynamic_cast ,因为它不是必需的根本就不是你的编译器生成的。 因此,在这种情况下,您的程序可以避免崩溃。

在Valgrind下运行时,我看到的第一个错误是

Invalid read of size 8
   at 0x4CCE92D: __dynamic_cast (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.22)
   by 0x109139: _ZZ4mainENKUlOT_E_clIRSt10unique_ptrI8DrawableSt14default_deleteIS4_EEEEDaS0_ (43706186.cpp:27)
   by 0x10917B: _ZN9__gnu_cxx5__ops10_Iter_predIZ4mainEUlOT_E_EclINS_17__normal_iteratorIPSt10unique_ptrI8DrawableSt14default_deleteIS9_EESt6vectorISC_SaISC_EEEEEEbS2_ (predefined_ops.h:241)
   by 0x10902D: _ZSt11__remove_ifIN9__gnu_cxx17__normal_iteratorIPSt10unique_ptrI8DrawableSt14default_deleteIS3_EESt6vectorIS6_SaIS6_EEEENS0_5__ops10_Iter_predIZ4mainEUlOT_E_EEESE_SE_SE_T0_ (stl_algo.h:866)
   by 0x108F78: _ZSt9remove_ifIN9__gnu_cxx17__normal_iteratorIPSt10unique_ptrI8DrawableSt14default_deleteIS3_EESt6vectorIS6_SaIS6_EEEEZ4mainEUlOT_E_ESC_SC_SC_T0_ (stl_algo.h:937)
   by 0x108EBC: main (43706186.cpp:25)
 Address 0x5892dc0 is 0 bytes inside a block of size 16 free'd
   at 0x4A0A2DB: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
   by 0x109BFC: Derived::~Derived() (43706186.cpp:15)
   by 0x109D21: std::default_delete<Drawable>::operator()(Drawable*) const (unique_ptr.h:76)
   by 0x10A7C4: std::unique_ptr<Drawable, std::default_delete<Drawable> >::reset(Drawable*) (unique_ptr.h:347)
   by 0x10A39D: std::unique_ptr<Drawable, std::default_delete<Drawable> >::operator=(std::unique_ptr<Drawable, std::default_delete<Drawable> >&&) (unique_ptr.h:254)
   by 0x109062: _ZSt11__remove_ifIN9__gnu_cxx17__normal_iteratorIPSt10unique_ptrI8DrawableSt14default_deleteIS3_EESt6vectorIS6_SaIS6_EEEENS0_5__ops10_Iter_predIZ4mainEUlOT_E_EEESE_SE_SE_T0_ (stl_algo.h:868)
   by 0x108F78: _ZSt9remove_ifIN9__gnu_cxx17__normal_iteratorIPSt10unique_ptrI8DrawableSt14default_deleteIS3_EESt6vectorIS6_SaIS6_EEEEZ4mainEUlOT_E_ESC_SC_SC_T0_ (stl_algo.h:937)
   by 0x108EBC: main (43706186.cpp:25)
 Block was alloc'd at
   at 0x4A0921F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
   by 0x10942E: std::_MakeUniq<Derived>::__single_object std::make_unique<Derived>() (unique_ptr.h:791)
   by 0x108DE2: main (43706186.cpp:21)

由此可见,添加到数组的元素之一已删除,但我们仍然尝试通过捕获的指针( selected )在lambda中进行dynamic_cast

如果将演员表移至“擦除删除”调用之外,则在删除元素之前,仅执行一次dynamic_cast

    auto const s2 = dynamic_cast<Drawable*>(selected);

    vec.erase(std::remove_if(vec.begin(), vec.end(), 
        [s2](auto&& ptr) { 
            return ptr.get() == s2; 
    }), vec.end());

此版本运行完整,没有来自Valgrind的警告。

顺便说一句,请注意,您可以编写lambda来接受const auto& ,因为您无意修改元素。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM