将函数传递给模板

Passing a function to a template

本文关键字:函数      更新时间:2023-10-16

我正在为我正在处理的应用程序编写一个 Win32Exception,头文件如下所示:

class Win32Exception : std::exception {
public:
    Win32Exception() = default;
    explicit Win32Exception(const std::wstring& message);
    explicit Win32Exception(const std::string& message);
    explicit Win32Exception(const char* message);
    virtual ~Win32Exception() throw() override = default;
    char const* what() const override;
    std::wstring message() const;
};

what函数的实现如下所示:

char const* Win32Exception::what() const
{
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER;
    DWORD errorCode = GetLastError();
    DWORD systemLocale = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
    LPSTR buffer = nullptr;
    DWORD length = FormatMessageA(flags, nullptr, errorCode, systemLocale, (LPSTR)&buffer, 0, nullptr);
    if(buffer != nullptr)
    {
        LocalFree(buffer);
    }
    else if (length == 0)
    {
        buffer = "Cannot get an error message. FormatMessage failed.";
    }
    return buffer;
}

message函数的实现如下所示:

std::wstring Win32Exception::message() const
{
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER;
    DWORD errorCode = GetLastError();
    DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
    LPWSTR buffer = nullptr;
    DWORD length = FormatMessageW(flags, nullptr, errorCode, systemLocale, (LPWSTR)&buffer, 0, nullptr);
    if (buffer != nullptr)
    {
        LocalFree(buffer);
    }
    else if (length == 0)
    {
        buffer = L"Cannot get an error message. FormatMessage failed.";
    }
    return buffer;
}

现在,我想摆脱这种重复,所以我想我可以用模板做一些事情,如下所示:

    template <typename TResult, DWORD (*formatMessageVersion)(DWORD, LPCVOID, DWORD, DWORD, TResult, DWORD, va_list*)>
TResult formatMessage(DWORD& systemLocale, DWORD& errorCode, TResult& fallbackMessage) const {
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER;
    TResult buffer = nullptr;
    DWORD length = formatMessageVersion(flags, nullptr, errorCode, systemLocale, (TResult)&buffer, 0, nullptr);
    if (buffer != nullptr)
    {
        LocalFree(buffer);
    }
    else if (length == 0)
    {
        buffer = fallbackMessage;
    }
    return buffer;
}

在我的一次尝试中,我试图将函数FormatMessageA/W作为formatMessage参数传递给 std::function,但我不确定为什么它不起作用......

我认为我使用它的方式如下:

char const* Win32Exception::what() const
{
    DWORD errorCode = GetLastError();
    DWORD systemLocale = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
    return formatMessage<LPSTR, FormatMessageA>(systemLocale,
                                                errorCode,
                                                "Cannot get an error message. FormatMessage failed.");
}
std::wstring Win32Exception::message() const
{
    DWORD errorCode = GetLastError();
    DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
    return formatMessage<LPWSTR, FormatMessageW>(systemLocale,
                                                errorCode,
                                                L"Cannot get an error message. FormatMessage failed.");
}

但是我收到一堆错误:

Severity    Code    Description Project File    Line    Suppression State
Error   C2664   'TResult Yalla::Win32Exception::formatMessage<LPSTR,DWORD FormatMessageA(DWORD,LPCVOID,DWORD,DWORD,LPSTR,DWORD,va_list *)>(DWORD &,DWORD &,TResult &) const': cannot convert argument 3 from 'const char [51]' to 'LPSTR &' RunBox  d:userseyalprojectscodeyallacoresrcrunboxwin32win32-exception.cpp 29  
Severity    Code    Description Project File    Line    Suppression State
Error   C2664   'TResult Yalla::Win32Exception::formatMessage<LPSTR,DWORD FormatMessageA(DWORD,LPCVOID,DWORD,DWORD,LPSTR,DWORD,va_list *)>(DWORD &,DWORD &,TResult &) const': cannot convert argument 3 from 'const char [51]' to 'LPSTR &' RunBox  d:userseyalprojectscodeyallacoresrcrunboxwin32win32-exception.cpp 29  
Severity    Code    Description Project File    Line    Suppression State
Error   C2664   'TResult Yalla::Win32Exception::formatMessage<LPWSTR,DWORD FormatMessageW(DWORD,LPCVOID,DWORD,DWORD,LPWSTR,DWORD,va_list *)>(DWORD &,DWORD &,TResult &) const': cannot convert argument 3 from 'const wchar_t [51]' to 'LPWSTR &'   RunBox  d:userseyalprojectscodeyallacoresrcrunboxwin32win32-exception.cpp 40  
Severity    Code    Description Project File    Line    Suppression State
Error   C2664   'TResult Yalla::Win32Exception::formatMessage<LPWSTR,DWORD FormatMessageW(DWORD,LPCVOID,DWORD,DWORD,LPWSTR,DWORD,va_list *)>(DWORD &,DWORD &,TResult &) const': cannot convert argument 3 from 'const wchar_t [51]' to 'LPWSTR &'   RunBox  d:userseyalprojectscodeyallacoresrcrunboxwin32win32-exception.cpp 40  
Severity    Code    Description Project File    Line    Suppression State
Error (active)      no instance of function template "Yalla::Win32Exception::formatMessage" matches the argument list   RunBox  d:UsersEyalProjectsCodeYallacoresrcrunboxwin32win32-exception.cpp 27  
Severity    Code    Description Project File    Line    Suppression State
Error (active)      no instance of function template "Yalla::Win32Exception::formatMessage" matches the argument list   RunBox  d:UsersEyalProjectsCodeYallacoresrcrunboxwin32win32-exception.cpp 38  

请记住,我正在重新学习C++并且多年来没有碰过它,所以如果您能告诉我如何改进代码或以更好的方式做任何我试图做的事情,我真的很高兴听到它! ;)

更新:您可以在GitHub上找到最终的解决方案,感谢大家。

不要通过模板解决重复问题。您已经有一个函数 message() ,用于调用FormatMessageW并获取消息文本。这就是您所需要的。若要实现what()需要一个 ANSI 字符串。因此,请先调用message()然后转换为ANSI。

换句话说,通过首先调用 message() 来实现what()。然后将std::wstring转换为std::string。然后返回c_str()产生的const char*。不过,您需要将std::string存储在成员字段中,以便c_str返回的指针保持有效。

要执行转换,请参阅此处显示的有关该主题的众多问题之一。例如:如何将 wstring 转换为字符串?

就目前而言,您当前的 what() 实现返回一个无效的指针,因为它对随后返回的值调用LocalFree。但是,如果您遵循我的建议,所有这些代码都会消失,所以不要太纠结于此。

您对FormatMessageW的错误处理是错误的。成功或其他方面由返回值指示,如文档所示。值为 0 表示失败。这就是您需要测试的内容。我怎么强调都不够强调非常仔细地阅读文档的重要性。我想说的是,我们在这里看到的>90% 的 winapi 问题在错误处理中都有错误。

更重要的是,在释放buffer后,您将再次访问它。代码应该更像这样:

LPWSTR buffer;
DWORD length = FormatMessageW(flags, nullptr, errorCode, systemLocale, (LPWSTR)&buffer, 
    0, nullptr);
if (length == 0)
    return L"Cannot get an error message. FormatMessage failed.";
std::wstring retval = buffer;
LocalFree(buffer);
return retval;

您应该允许向类传递错误代码。一些错误代码由 API 直接返回,例如注册表 API。此外,在您设法获得调用GetLastError的代码之前,您有时会对SetLastError运行时调用C++感到困惑。

直接问题在此处的错误消息中描述:

[...] cannot convert argument 3 from 'const char [51]' to 'LPSTR &' [...]

您正在将字符文本传递给需要非常量引用的函数。您不能将非常量引用绑定到临时引用,因此简单的解决方法只是将最后一个参数类型从 TResult& 更改为 TResult const& 。这应该可以解决您的问题。