65.9K
CodeProject 正在变化。 阅读更多。
Home

Boost Lexical Cast 包装器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2017年2月5日

CPOL

1分钟阅读

viewsIcon

12168

downloadIcon

93

Boost Lexical Cast 包装器, 以提供更多异常信息

不友好的异常信息

#include <iostream>
#include <stdexcept>
#include <boost/lexical_cast.hpp>

int main()
{
    const char* str = "aa";
    try
    {
        int result = boost::lexical_cast<int>(str);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

以下是显示的异常信息,如果涉及多个 lexical_cast 调用,我们无法从该信息中得知哪个类型转换失败。

bad lexical cast: source type value could not be interpreted as target

让我们使用 std::string 类型再次尝试。

#include <string>
#include <iostream>
#include <stdexcept>
#include <boost/lexical_cast.hpp>

int main()
{
    const std::string str = "aa";
    try
    {
        int result = boost::lexical_cast<int>(str);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

使用 std::string 类型时,显示相同的异常信息。

bad lexical cast: source type value could not be interpreted as target

包装 lexical_cast 以获取函数签名

让我们包装 lexical_cast,使其抛出带有函数签名信息的 std::runtime_error

#include <string>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <boost/lexical_cast.hpp>

#ifdef _WIN32
#define MY_FUNC_SIG __FUNCSIG__
#else
#define MY_FUNC_SIG __PRETTY_FUNCTION__
#endif

template <typename Target, typename Source>
inline Target lexical_cast_wrapper(const Source &arg)
{
    Target result;

    try
    {
        result = boost::lexical_cast<Target>(arg);
    }
    catch (boost::bad_lexical_cast&)
    {
        std::ostringstream oss;
        oss << "bad_lexical_cast exception thrown:\n";
        oss << "Source arg:<" << arg << ">\n";
        oss << "Function sig:" << MY_FUNC_SIG;
        throw std::runtime_error(oss.str().c_str());
    }

    return result;
}

int main()
{
    const std::string str = "aa";
    try
    {
        int result = lexical_cast_wrapper<int>(str);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

来自 VC++ 的 const char* 源参数的输出。

int __cdecl lexical_cast_wrapper<int,const char*>(const char *const &)

来自 GCC 和 Clang 的 const char* 源参数的输出。

Target lexical_cast_wrapper(const Source&) [with Target = int; Source = const char*]

来自 VC++ 的 std::string 源参数的输出。

int __cdecl lexical_cast_wrapper<int,class std::basic_string<char,struct std::char_traits<char,class std::allocator<char> >>(const class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &)

来自 GCC 和 Clang 的 std::string 源参数的输出。

Target lexical_cast_wrapper(const Source&) [with Target = int; Source = std::basic_string<char>]

下一步是手动编写 2 个解析器(1 个用于 VC++,另一个用于 GCC/Clang)来提取源参数和目标参数类型。

#include <string>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <stack>
#include <boost/lexical_cast.hpp>

#ifdef _WIN32
#define MY_FUNC_SIG __FUNCSIG__
#else
#define MY_FUNC_SIG __PRETTY_FUNCTION__
#endif


#ifdef _WIN32
std::string replace_string_type(const std::string& long_str_type)
{
    std::string result = (long_str_type.find("const") != std::string::npos) ? "const " : "";
    if (long_str_type.find("std::basic_string<char") != std::string::npos)
        result += "std::string";
    else if (long_str_type.find("std::basic_string<wchar_t") != std::string::npos)
        result += "std::wstring";
    else if (long_str_type.find("std::basic_string<char16_t") != std::string::npos)
        result += "std::u16string";
    else if (long_str_type.find("std::basic_string<char32_t") != std::string::npos)
        result += "std::u32string";
    else if (long_str_type.find("std::pmr::basic_string<char") != std::string::npos)
        result += "std::pmr::string";
    else if (long_str_type.find("std::pmr::basic_string<wchar_t") != std::string::npos)
        result += "std::pmr::wstring";
    else if (long_str_type.find("std::pmr::basic_string<char16_t") != std::string::npos)
        result += "std::pmr::u16string";
    else if (long_str_type.find("std::pmr::basic_string<char32_t") != std::string::npos)
        result += "std::pmr::u32string";
    else
        result = long_str_type;

    return result;
}

bool getTypes(const std::string& func, std::string& src_type, std::string& target_type)
{
    src_type = ""; 
    target_type = "";
    static const std::string func_prelude = "lexical_cast_wrapper<";
    size_t pos = func.find(func_prelude);
    if (pos != std::string::npos)
    {
        pos += func_prelude.size();
        std::stack<bool> bstack; // type of the stack does not matter
        bstack.push(true);
        bool parsing_target = true;
        while (pos < func.size())
        {
            if (func[pos] == '<')
                bstack.push(true);
            else if (func[pos] == '>')
            {
                bstack.pop();
                if (bstack.size() == 0)
                    break;
            }

            if (func[pos] == ','&&bstack.size() == 1)
            {
                parsing_target = false;
                ++pos;
                continue;
            }

            if (parsing_target)
                target_type += func[pos];
            else
                src_type += func[pos];

            ++pos;
        }

        src_type    = replace_string_type(src_type);
        target_type = replace_string_type(target_type);

        return true;
    }
    return false;
}
#else
bool getTypes(const std::string& func, std::string& src_type, std::string& target_type)
{
    src_type = "";
    target_type = "";
    static const std::string prelude = "[with Target = ";
    static const std::string prelude2 = "; Source = ";

    size_t pos = func.find(prelude);
    if (pos != std::string::npos)
    {
        pos += prelude.size();
        size_t pos2 = func.find(prelude2, pos);
        if (pos2 != std::string::npos)
        {
            target_type = func.substr(pos, pos2 - pos);
            pos2 += prelude2.size();
            size_t posEnd = func.find("]", pos2);
            if (posEnd != std::string::npos)
            {
                src_type = func.substr(pos2, posEnd - pos2);
                return true;
            }
        }
    }
    return false;
}
#endif

template <typename Target, typename Source>
inline Target lexical_cast_wrapper(const Source &arg)
{
    Target result;
    try
    {
        result = boost::lexical_cast<Target>(arg);
    }
    catch (boost::bad_lexical_cast&)
    {
        std::string src_type = ""; std::string target_type = "";
        if (getTypes(MY_FUNC_SIG, src_type, target_type))
        {
            std::ostringstream oss;
            oss << "bad_lexical_cast exception thrown:";
            oss << "\nSource arg:" << arg;
            oss << "\nSource type:" << src_type;
            oss << "\nTarget type:" << target_type;
            throw std::runtime_error(oss.str().c_str());
        }
    }
    return result;
}

int main()
{
    //const char* str = "aa";
    const std::string str = "aa";
    try
    {
        int result = lexical_cast_wrapper<int>(str);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

对于 VC++ 输出,我选择显示 typedef'ed 类型,而不是完整的类型信息。对于 GCC 和 Clang,我按原样显示类型。输出如下所示。

VC++
bad_lexical_cast exception thrown:
Source arg:aa
Source type:std::string
Target type:int
GCC and Clang
bad_lexical_cast exception thrown:
Source arg:aa
Source type:std::basic_string<char>
Target type:int

注意:lexical_cast_wrapper 抛出 std::runtime_error,而不是 bad_lexical_cast

如果 Boost lexical_cast 的开发者能够在 lexical_cast 内部实现此解析,那就太好了。对于我的用例,需要解析一个包含 50 行 10 列的配置文件,这个包装器可以帮助快速缩小问题范围。此包装器只有在出现异常的情况下才会产生较高的开销。如果开发者只需要在测试期间使用它,可以定义一个 LEX_CAST 宏来相应地定向调用,并且必须将捕获的异常类型从 bad_lexical_cast 更改为 std::exception

#ifdef _DEBUG
	#define LEX_CAST lexical_cast_wrapper
#else
	#define LEX_CAST boost::lexical_cast
#endif

Github

© . All rights reserved.