使用普通函数删除器构建std :: unique_ptr的包装器

时间:2017-04-12 19:40:14

标签: c++ c++11 templates c++14

我正在尝试实施一个std::unique_ptr工厂,我可以这样使用:

auto fd = my_make_unique<fclose>(fopen("filename", "r"));

即,将删除函数作为模板参数传递。

我在C ++ 11中的最佳尝试是:

template<typename D, D deleter, typename P>
struct Deleter {
    void operator()(P* ptr) {
        deleter(ptr);
    }
};

template<typename D, D deleter, typename P>
std::unique_ptr<P, Deleter<D, deleter, P>> my_make_unique(P* ptr)
{
    return std::unique_ptr<P, Deleter<D, deleter, P>>(ptr);
}

在C ++ 14中它更清晰:

template<typename D, D deleter, typename P>
auto my_make_unique(P* ptr)
{
    struct Deleter {
        void operator()(P* ptr) {
            deleter(ptr);
        }
    };
    return std::unique_ptr<P, Deleter>(ptr);
}

但是这两种解决方案都要求我在&fclose之前将fclose的类型作为模板参数传递:

auto fd = my_make_unique<decltype(&fclose), fclose>(fopen("filename", "r"));

是否有可能摆脱C ++ 11中的decltype(&fclose)模板参数?在C ++ 14中怎么样?

编辑:为什么这个问题不是RAII and smart pointers in C++的重复:引用的问题是关于C ++中的一般RAII技术,以及std::unique_ptr可以用于此目的的答案之一。我已经熟悉RAII模式以及std::unique_ptr如何成为一个解决方案,但是我关注如何在与C库交互时遇到的这种常见情况构建一个更易于使用的抽象的当前问题。

4 个答案:

答案 0 :(得分:4)

实用方法:将删除器设为运行时参数。

template<typename P, typename D>
auto my_make_unique(P* ptr, D deleter)
{
    return std::unique_ptr<P, D>(ptr, deleter);
}

int main()
{
    auto fd = my_make_unique(fopen("filename", "r"), fclose);     
}

答案 1 :(得分:4)

  

是否有可能摆脱C ++ 11中的decltype(&fclose)模板参数?在C ++ 14中怎么样?

不,直到C ++ 17才能摆脱该参数的类型。模板非类型参数需要一种类型,您无法推断 - 因为它必须是模板非类型参数。这是一个问题。

此外,您遇到的问题是未指定标准库中的函数地址。例如,标准库始终允许提供额外的重载,因此&fclose可能无效。唯一真正可移植的方法是提供lambda或编写自己的包装函数:

auto my_fclose_lam = [](std::FILE* f) { std::fclose(f); }
void my_fclose_fun(std::FILE* f) { std::fclose(f); }

对于其中任何一个,最好使用C ++ 14,你可以引入一个宏,如:

#define DECL(v) decltype(v), v
auto fd = my_make_unique<DECL(my_fclose_lam)>(fopen("filename", "r"));

C ++ 17允许您至少通过template auto将自定义函数提升为模板参数(虽然还不是lambda):

template <auto deleter, typename P>
auto my_make_unique(P* ptr)
{
    struct Deleter {
        void operator()(P* ptr) {
            deleter(ptr);
        }
    };
    return std::unique_ptr<P, Deleter>(ptr);
}

my_make_unique<my_fclose_fun>(fopen(...));

C ++ 20最终将允许你将lambda粘贴到那里:

my_make_unique<[](std::FILE* f){ std::fclose(f); }>(fopen(...));

错误的回答:

所以你能做的最好就是引入一个像:

这样的宏
#define DECL(v) decltype(v), v
auto fd = my_make_unique<DECL(&fclose)>(fopen("filename", "r"));

您是否认为这是一个好主意可能取决于您的同事。

在C ++ 17中,使用template auto,您可以编写my_make_unique<fclose>,这很棒:

template <auto deleter, typename P>
auto my_make_unique(P* ptr)
{
    struct Deleter {
        void operator()(P* ptr) {
            deleter(ptr);
        }
    };
    return std::unique_ptr<P, Deleter>(ptr);
}

答案 2 :(得分:2)

另一种解决方法是使用函数的完全签名:

template<typename T, int (*P)(T*)> //for `fclose`
auto my_make_unique(T*) { ... }

template<typename T, void (*P)(T*)> //for other function signatures
auto my_make_unique(T*) { ... }

//etc.

auto uniq = my_make_unique<File, fclose>(fopen("filename", "r"));

这不是通用解决方案,但在95%的情况下都可以使用。

答案 3 :(得分:0)

std::unique_ptr指针创建FILE*的典型方法是:

auto fd = std::unique_ptr<FILE, decltype(fclose)>(fopen(...), fclose);

您可以将其包装在宏中:

#define my_make_unique(ptr, deleter) \
    std::unique_ptr<std::remove_pointer<decltype<ptr>>::type, d>(ptr, deleter)

然后像这样使用它:

auto fd = my_make_unique(fopen(...), fclose);