c – 带有类型擦除析构函数的unique_ptr不能正常工作(使用警告)

有一个很好的小技巧
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< Foo> f(makeFoo());,警告不会显示.

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

要重现,请按上面的步骤创建3个文件erasedptr.hpp,main.cpp,foo.cpp并进行编译.

所以问题是:发生了什么?可以有任何替代实施来规避这种警告吗?

最佳答案 您的问题是有效的,但您尝试使用的代码有点令人费解,所以让我赶快去找到想要的解决方案.

>你在这里做的不是类型擦除(Andrzej也是错误的) – 你只是将删除捕获到运行时函数值,没有任何好处BTW.类型擦除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>() };
}

main.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 / template别名:

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

main.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
}
点赞