【问题标题】:unique_ptr with type erased destructor does not quite work (works with warnings)具有类型擦除析构函数的 unique_ptr 不太有效(适用于警告)
【发布时间】:2016-06-26 01:47:23
【问题描述】:

有一个很好的小技巧here 允许使用不完整类型的std::unique_ptr

以下是相关代码:

// File: erasedptr.h
#include <memory>
#include <functional>

// type erased deletor (an implementation type using "veneer")
template <typename T>
struct ErasedDeleter : std::function<void(T*)>
{
    ErasedDeleter()
        : std::function<void(T*)>( [](T * p) {delete p;} )
    {}
};

// A unique_ptr typedef
template <typename T>
using ErasedPtr = std::unique_ptr<T, ErasedDeleter<T>>;


// Declare stuff with an incomplete type
struct Foo;
ErasedPtr<Foo> makeFoo();


// File: main.cpp (Foo's definition is not available in this translation unit)
#include "erasedptr.h"
int main() {
    ErasedPtr<Foo> f;  // [R1]
    f = makeFoo();
    // ~Foo() gets called fine
}

// File: foo.cpp
#include <iostream>
#include "erasedptr.h"
struct Foo {
    ~Foo() { std::cout << "~Foo()\n" ; }
};
ErasedPtr<Foo> makeFoo() { return ErasedPtr<Foo>(new Foo); }

这适用于我尝试过的所有编译器:gcc 4.9、clang 3.5 以及 msvc VS13 和 VS15。但它们都会产生以下警告:

 deletion of pointer to incomplete type 'Foo'; no destructor called

如果将上面的 [R1] 替换为 ErasedPtr&lt;Foo&gt; f( makeFoo() );,则不会出现警告。

最后,析构函数确实被调用,似乎没有实际问题。该警告是有问题的,因为在质量关键的环境中不能忽略它,并且这种非常有用的模式不可用。

要重现,请创建 3 个文件 erasedptr.hppmain.cppfoo.cpp 并编译。

所以问题是:发生了什么?是否有任何替代实现来规避此警告?

【问题讨论】:

  • 我认为这只有在makeFoo 可以看到Foo 的定义以便ErasedDeleter 可以正确调用析构函数时才有效。
  • makeFoo 的定义确实看到了Foo 的定义(很明显,因为它也是newed)。正如我所指出的,它在这方面工作得很好。问题是默认构造的ErasedPtr&lt;Foo&gt; 似乎导致了这个虚假(?)警告。
  • 我几乎倾向于认为编译器是正确的,而这种模式根本不安全......
  • 尝试使用virtual析构函数。我的猜测是,这可能会使问题更加明显。但我认为这可能是Delete objects of incomplete typeWhy, really, deleting an incomplete type is undefined behaviour? 的重复项,据我了解,如果Foo 具有非平凡的析构函数(在这种情况下确实如此),则这是未定义的行为。
  • 这本质上是一个 unique_ptr,无论它维护的对象是否具有虚拟析构函数都是无关紧要的(它可能是一个非类)。删除不完整的类型当然是未定义的,因为没有调用析构函数(内存被释放,但谁知道对象持有的其他资源)。问题不在于删除不完整类型是否有问题。问题是为什么它会发生在这种特定的模式...

标签: c++ c++11 unique-ptr incompatibility incomplete-type


【解决方案1】:

您的问题是有效的,但您尝试使用的代码有点令人费解,所以让我继续寻找所需的解决方案。

  • 您在这里所做的不是类型擦除(Andrzej 对此也是错误的) - 您只是将删除捕获到运行时函数值中,顺便说一句,没有任何好处。类型擦除 OTH 是指其他代码部分丢失有关初始类型的信息。
  • 我在 VS 2015 上尝试了你的代码,它也调用了析构函数 ~Foo(),但这只是运气好,这意味着编译器正在做一些花哨的事情。如果您不使用 std::function 而是编写自己的自定义删除器,则不会调用析构函数。

解决方案 1 - 类型擦除

如果您真的想擦除类型,您可以通过以下方式编写/使用 ErasedPtr:

erasedptr.h

// file erasedptr.h

#include <memory>
#include <functional>

// make type erased deleter
template <typename T>
std::function<void(void*)> makeErasedDeleter()
{
    return {
        [](void* p) {
            delete static_cast<T*>(p);
        }
    };
};

// A unique_ptr typedef
template <typename T>
using ErasedPtr = std::unique_ptr<T, std::function<void(void*)>>;

foo.cpp

// file foo.cpp

#include <iostream>
#include "erasedptr.h."

struct Foo {
    ~Foo() { std::cout << "~Foo()\n" ; }
};

// capture creation and deletion of Foo in this translation unit
ErasedPtr<Foo> makeFoo() {
    return { new Foo, makeErasedDeleter<Foo>() };
}

ma​​in.cpp

// file main.cpp (Foo's definition is not available in this translation unit)

#include "erasedptr.h"

// fwd decl Foo
struct Foo;
ErasedPtr<Foo> makeFoo();

int main() {
    ErasedPtr<Foo> f;  // [R1]
    f = makeFoo();
    // ~Foo() gets called fine
}

这种方式只有 foo.cpp 需要知道实际类型并将删除捕获到std::function

解决方案 2 - 不完整的类型,真的

你真正想要的是处理不完整的类型。您对 STL 的默认删除器 std::default_delete 的“问题”是它在编译时断言删除是否安全 - 这该死的对!

要使其正常工作,就是告诉编译器/链接器您真正关心删除的正确实现,使用显式模板实例化。这样,您的唯一指针就不需要任何特殊的 typedef/模板别名:

foo.cpp

// file foo.cpp

#include "foo_fwddecl.h"

// capture creation of Foo in this translation unit
std::unique_ptr<Foo> makeFoo() {
    return std::make_unique<Foo>();
}

// explicitly instantiate deletion of Foo in this translation unit
template void std::default_delete<Foo>::operator()(Foo*) const noexcept;
template void std::default_delete<const Foo>::operator()(const Foo*) const noexcept;
// note: possibly instantiate for volatile/const volatile modifiers

foo_fwddecl.h

#include <memory>

struct Foo;

std::unique_ptr<Foo> makeFoo();

extern template void std::default_delete<Foo>::operator()(Foo*) const noexcept;
extern template void std::default_delete<const Foo>::operator()(const Foo*) const noexcept;
// note: possibly instantiate for volatile/const volatile modifiers

ma​​in.cpp

// file main.cpp (Foo's definition is not available in this translation unit)

#include "foo_fwddecl.h"

int main() {
    std::unique_ptr<Foo> f;  // [R1]
    f = makeFoo();
    // ~Foo() gets called fine
}

【讨论】:

    猜你喜欢
    • 2014-08-23
    • 1970-01-01
    • 2020-05-29
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 2013-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多