【问题标题】:Custom STL deleter for any type of resource适用于任何类型资源的自定义 STL 删除器
【发布时间】:2021-04-28 14:42:46
【问题描述】:

我检查了许多有关自定义清洁器的链接,例如 C++ Destructors with Vectors, Pointers,,但仍然没有找到答案。
对于那些对解决方案感兴趣的人,请跳转到问题底部的更新 1/2。感谢积极和细心的参与者,在讨论过程中找到了解决方案 1/2。 我想为 something 制作一个自定义的自动删除器。让我们看看下面的示例

#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
....
int HeigthMapFromImg(const wchar_t* imgPath, std::vector<std::vector<float>>& heights)
{
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    int x = HeightMapFromImgApi(imgPath, heights);

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return x;
}

注意,std::unique_ptr, deleters and the Win32 API 不是答案。问题不在于编写自己的包装器,也不在于 WinAPI。 One-liner for RAII on non pointer? 是一个类似的问题,但答案是过度设计的。
在示例中,GDI+ 令牌用于在工作完成时调用 GdiplusShutdown。我想用 unique_ptr 自动化它,我正在尝试这样的事情

int HeigthMapFromImg(const wchar_t* imgPath, std::vector<std::vector<float>>& heights)
{
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    std::unique_ptr<ULONG_PTR, decltype(Gdiplus::GdiplusShutdown) > ptr(gdiplusToken, Gdiplus::GdiplusShutdown);
    int x = HeightMapFromImgApi(imgPath, heights);

    return x;
}

但是不管我怎么做都不编译,代码是用VisualStudio写的

1>...BitmapReader.cpp(145): message : see reference to class template instantiation 'std::unique_ptr<ULONG_PTR,void (ULONG_PTR)>' being compiled
1>...BitmapReader.cpp(145,109): error C2660: 'std::unique_ptr<ULONG_PTR,void (ULONG_PTR)>::unique_ptr': function does not take 2 arguments
1>...\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\include\memory(2686,5): message : see declaration of 'std::unique_ptr<ULONG_PTR,void (ULONG_PTR)>::unique_ptr'

STL 指针包装器非常适用于任何看起来像指针但 GDI+ 标记被定义为 ULONG_PTR 的东西,它被定义为 __int64。我希望它是 PTR,但事实并非如此。我用指针的时候是没问题的,比如wchar_t的缓冲区:

std::wostream& ShowLastError(std::wostream& os)
{
    auto localdeleterw = [](wchar_t* a) {::LocalFree(a); };
    wchar_t* pBuffer = 0;
    int ret = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), 0, (LPWSTR)&pBuffer, 0, 0);
    if (!(ret && pBuffer))
    {
        os << L"failed to read error";
        return os;
    }
    std::unique_ptr<wchar_t, decltype(localdeleterw)> ptr (pBuffer, localdeleterw);
    os << pBuffer << std::flush;
    return os;
}

为了期待编写包装器,我已经编写了自己的包装器,它更简洁:

template<class T, auto deleter> struct cleaner
{
    T resource;
    ~cleaner() { deleter (resource); }
};

它工作得很好,但我仍然觉得它可以在没有我自己的包装器/解决方法的情况下完成:

int HeigthMapFromImg(const wchar_t* imgPath, std::vector<std::vector<float>>& heights)
{
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    cleaner<ULONG_PTR, Gdiplus::GdiplusShutdown>  c{ gdiplusToken };
    int x = HeightMapFromImgApi(imgPath, heights);

    return x;
}

答案:
更新 1:
正如 apple_apple 所指出的(谢谢),需要一个指针,所以我通过传递 &gdiplusToken 而不是 gdiplusToken 将它转换为一个指针,然后在更清洁的函数中间接它,它有效,但看起来仍然是一种解决方法:

auto dtr = [](ULONG_PTR* a) {Gdiplus::GdiplusShutdown(*a); };
std::unique_ptr<ULONG_PTR, decltype(dtr) > ptr(&gdiplusToken, dtr);

更新 2:
作为实验性解决方案,Igor Tandetnik 的建议(谢谢)也是一种选择,但看起来不是 VisualStudio 的一部分 https://en.cppreference.com/w/cpp/experimental/scope_exit
更新 3:
建议编写包装器的答案,请仔细阅读该问题。我已经有了自己的,请参阅关于 cleaner 类的部分。无论如何,我再次强调它:

template<class T, auto deleter> struct cleaner
{
    T resource;
    ~cleaner() { deleter (resource); }
};
///// and using
...
{
....
    cleaner<ULONG_PTR, Gdiplus::GdiplusShutdown>  c{ gdiplusToken };

【问题讨论】:

  • 我认为问题是std::unique_ptr&lt;ULONG_PTR&gt;实际上存储了一个指向ULONG_PTR的指针,所以删除器不匹配(应该看起来像void(ULONG_PTR*))?
  • 你不能盲目地将ULONG_PTR 转换为指针,这根本不安全
  • std::unique_ptr&lt;T, Deleter&gt; 想将T* 传递给删除器。但是GdiplusShutdown 不采用指针类型。您可以在进入std::unique_ptr 的过程中将ULONG_PTR 转换为某个指针类型,但std::unique_ptr 在调用删除器时不会将其转换回。你可以编写自己的删除器,它接受一个指针,将其转换并传递给GdiplusShutdown,我想。
  • 但是到那时,使用适当的范围保护类会更好,例如 std::experimental::scope_exitsimilar
  • @armagedescu 是的,它是这样工作的。我的意思是你不应该reinterpret_cast

标签: c++ stl c++14


【解决方案1】:

来自 cmets 的总结:


ScopeGuard(由@IgorTandetnik 建议)以及@OP 可能会问的问题


实际智能指针(@RemyLebeau 建议)


来自@OP(即ScopeGuard)的解决方案

template<typename T, auto deleter> struct cleaner
{
   T resource;
   ~cleaner() { deleter(resource); }
};

/// and using
{
    cleaner<ULONG_PTR, Gdiplus::GdiplusShutdown>  c{ gdiplusToken };
    // do something here
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-22
    • 2017-12-28
    相关资源
    最近更新 更多