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