能否将 boost::system::error_code 转换为 std::error_code?

22

我希望尽可能用标准C++替换外部库(如boost),以最小化依赖关系。因此,我想知道是否存在安全的方法将boost::system::error_code转换为std::error_code。 伪代码示例:

void func(const std::error_code & err)
{
    if(err) {
        //error
    } else {
        //success
    }
}

boost::system::error_code boost_err = foo(); //foo() returns a boost::system::error_code
std::error_code std_err = magic_code_here; //convert boost_err to std::error_code here
func(std_err);

最重要的不是完全相同的错误,只要尽可能接近,最后看是不是一个错误。是否有聪明的解决方案?

提前致谢!


你想同时使用这两个吗?如果不是,那么这两个接口是否足够相似,以至于只需要简单的“搜索/替换”就可以完成呢? - ereOn
1
不可能的。std::error_code和boost::system::error_code都被使用了,但我已经成功地将boost::system::error_code抽象化为用户不可见,因此在未来当最后一个依赖项移除它时,我也可以这样做。 - Fredrik
我对这两个API都不太了解,无法给你提供magic_code,但是我可以说,逐步推进的最佳方式是使用#ifdef USE_BOOSTtypedef boost::system::error_code ErrorCodeType;相结合,以及一个#elsetypedef std::error_code ErrorCodeType;。然后,您可以逐步更改代码库,以便使用相同的接口调用支持两者,并且当所有内容在未定义USE_BOOST的情况下正常工作时,您可以永久切换。否则,您将最终忘记在侧流上工作。 - Dennis
从Boost版本1.65开始,boost::error_code可以隐式转换为std::error_code - Emile Cormier
4个回答

13
我曾经有过同样的问题,因为我想使用std::error_code,但同时也在使用其他使用boost::system::error_code(例如boost ASIO)的boost库。接受的答案适用于由std::generic_category()处理的错误代码,因为它们是从boost的通用错误代码简单转换而来,但对于您想要处理自定义错误类别的一般情况则不适用。
因此,我创建了以下代码作为通用的boost::system::error_code-to-std::error_code转换器。它通过动态创建每个boost::system::error_categorystd::error_category shim来工作,并将调用转发到底层的Boost错误类别。由于错误类别需要是单例(或者至少像这种情况一样类似于单例),因此我不希望会有太多内存爆炸。
我还只是将boost::system::generic_category()对象转换为使用std::generic_category(),因为它们应该表现相同。我本来想为system_category()做同样的事情,但在VC++10上测试时,它打印出了错误的消息(我认为它应该打印出您从FormatMessage获得的内容,但它似乎使用strerror,而Boost使用了预期的FormatMessage)。
要使用它,只需调用下面定义的BoostToErrorCode()
只是一个警告,我今天刚写完这个,所以只进行了基本测试。您可以任意使用它,但风险自负。
//==================================================================================================
// These classes implement a shim for converting a boost::system::error_code to a std::error_code.
// Unfortunately this isn't straightforward since it the error_code classes use a number of
// incompatible singletons.
//
// To accomplish this we dynamically create a shim for every boost error category that passes
// the std::error_category calls on to the appropriate boost::system::error_category calls.
//==================================================================================================
#include <boost/system/error_code.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/once.hpp>
#include <boost/thread/locks.hpp>

#include <system_error>
namespace
{
    // This class passes the std::error_category functions through to the
    // boost::system::error_category object.
    class BoostErrorCategoryShim : public std::error_category
    {
    public:
        BoostErrorCategoryShim( const boost::system::error_category& in_boostErrorCategory )
            :m_boostErrorCategory(in_boostErrorCategory), m_name(std::string("boost.") + in_boostErrorCategory.name()) {}

        virtual const char *name() const;
        virtual std::string message(value_type in_errorValue) const;
        virtual std::error_condition default_error_condition(value_type in_errorValue) const;

    private:
        // The target boost error category.
        const boost::system::error_category& m_boostErrorCategory;

        // The modified name of the error category.
        const std::string m_name;
    };

    // A converter class that maintains a mapping between a boost::system::error_category and a
    // std::error_category.
    class BoostErrorCodeConverter
    {
    public:
        const std::error_category& GetErrorCategory( const boost::system::error_category& in_boostErrorCategory )
        {
            boost::lock_guard<boost::mutex> lock(m_mutex);

            // Check if we already have an entry for this error category, if so we return it directly.
            ConversionMapType::iterator stdErrorCategoryIt = m_conversionMap.find(&in_boostErrorCategory);
            if( stdErrorCategoryIt != m_conversionMap.end() )
                return *stdErrorCategoryIt->second;

            // We don't have an entry for this error category, create one and add it to the map.                
            const std::pair<ConversionMapType::iterator, bool> insertResult = m_conversionMap.insert(
                ConversionMapType::value_type(
                    &in_boostErrorCategory, 
                    std::unique_ptr<const BoostErrorCategoryShim>(new BoostErrorCategoryShim(in_boostErrorCategory))) );

            // Return the newly created category.
            return *insertResult.first->second;
        }

    private:
        // We keep a mapping of boost::system::error_category to our error category shims.  The
        // error categories are implemented as singletons so there should be relatively few of
        // these.
        typedef std::unordered_map<const boost::system::error_category*, std::unique_ptr<const BoostErrorCategoryShim>> ConversionMapType;
        ConversionMapType m_conversionMap;

        // This is accessed globally so we must manage access.
        boost::mutex m_mutex;
    };


    namespace Private
    {
        // The init flag.
        boost::once_flag g_onceFlag = BOOST_ONCE_INIT;

        // The pointer to the converter, set in CreateOnce.
        BoostErrorCodeConverter* g_converter = nullptr;

        // Create the log target manager.
        void CreateBoostErrorCodeConverterOnce()
        {
            static BoostErrorCodeConverter converter;
            g_converter = &converter;
        }
    }

    // Get the log target manager.
    BoostErrorCodeConverter& GetBoostErrorCodeConverter()
    {
        boost::call_once( Private::g_onceFlag, &Private::CreateBoostErrorCodeConverterOnce );

        return *Private::g_converter;
    }

    const std::error_category& GetConvertedErrorCategory( const boost::system::error_category& in_errorCategory )
    {
        // If we're accessing boost::system::generic_category() or boost::system::system_category()
        // then just convert to the std::error_code versions.
        if( in_errorCategory == boost::system::generic_category() )
            return std::generic_category();

        // I thought this should work, but at least in VC++10 std::error_category interprets the
        // errors as generic instead of system errors.  This means an error returned by
        // GetLastError() like 5 (access denied) gets interpreted incorrectly as IO error.
        //if( in_errorCategory == boost::system::system_category() )
        //  return std::system_category();

        // The error_category was not one of the standard boost error categories, use a converter.
        return GetBoostErrorCodeConverter().GetErrorCategory(in_errorCategory);
    }


    // BoostErrorCategoryShim implementation.
    const char* BoostErrorCategoryShim::name() const
    {
        return m_name.c_str();
    }

    std::string BoostErrorCategoryShim::message(value_type in_errorValue) const
    {
        return m_boostErrorCategory.message(in_errorValue);
    }

    std::error_condition BoostErrorCategoryShim::default_error_condition(value_type in_errorValue) const
    {
        const boost::system::error_condition boostErrorCondition = m_boostErrorCategory.default_error_condition(in_errorValue);

        // We have to convert the error category here since it may not have the same category as
        // in_errorValue.
        return std::error_condition( boostErrorCondition.value(), GetConvertedErrorCategory(boostErrorCondition.category()) );
    }
}

std::error_code BoostToErrorCode( boost::system::error_code in_errorCode )
{
    return std::error_code( in_errorCode.value(), GetConvertedErrorCategory(in_errorCode.category()) );
}

你还在使用这段代码吗?它是否足够有用,可以贡献给 Boost 项目呢? - sehe
2
据我所知,它仍在使用中。我可以看到它作为一个有用的补充被添加到boost中,因为从概念上讲,boost和std版本的错误代码做的事情完全相同,只是由于类型系统不兼容而无法共存。但在这种情况下,最好直接在boost错误类别中实现它。这将消除互斥锁和映射的需求,并使转换为noexcept,代价是每个类别多几个字节。或者也许它可以直接从std派生,因为您可能还想能够从std->boost进行转换? - Screndib
截至Boost版本1.65,error_codeerror_categoryerror_condition现在可以转换为它们的C++11对应项。请参阅我的回答。 - Emile Cormier

9
自从C++-11(std::errc)以来,boost/system/error_code.hpp将相同的错误代码映射到system_error系统头文件中定义的std::errc。您可以比较两个枚举类型,它们在功能上应该是等效的,因为它们都基于POSIX标准。可能需要进行转换。例如:
namespace posix_error
    {
      enum posix_errno
      {
        success = 0,
        address_family_not_supported = EAFNOSUPPORT,
        address_in_use = EADDRINUSE,
        address_not_available = EADDRNOTAVAIL,
        already_connected = EISCONN,
        argument_list_too_long = E2BIG,
        argument_out_of_domain = EDOM,
        bad_address = EFAULT,
        bad_file_descriptor = EBADF,
        bad_message = EBADMSG,
        ....
       }
     }

以及 std::errc

address_family_not_supported  error condition corresponding to POSIX code EAFNOSUPPORT  

address_in_use  error condition corresponding to POSIX code EADDRINUSE  

address_not_available  error condition corresponding to POSIX code EADDRNOTAVAIL  

already_connected  error condition corresponding to POSIX code EISCONN  

argument_list_too_long  error condition corresponding to POSIX code E2BIG  

argument_out_of_domain  error condition corresponding to POSIX code EDOM  

bad_address  error condition corresponding to POSIX code EFAULT 

4
谢谢,我通过使用以下代码使其正常工作:std::make_error_code(static_caststd::errc::errc(err.value())) - 这里的 err 是 boost::system::error_code 的实例/引用。 - Fredrik
这不适用于 Boost.Asio 中的一般情况,它有其自己的错误类别。 - Emile Cormier

2
我将上述解决方案改进为更短的解决方案。使用 thread_local std::map 将类别名称映射到其实例,因此不需要锁定。但是,由于类别指针不同,所以无法在线程之间传递错误代码(如果您不想使用 thread_local 存储,则将其转换为锁定函数很简单)。另外,我认为它更加紧凑。
#include <iostream>
#include <map>
#include <boost/system/system_error.hpp>

namespace std
{

error_code make_error_code(boost::system::error_code error)
{
    struct CategoryAdapter : public error_category
    {
        CategoryAdapter(const boost::system::error_category& category)
            : m_category(category)
        {
        }

        const char* name() const noexcept
        {
            return m_category.name();
        }

        std::string message(int ev) const
        {
            return m_category.message(ev);
        }

    private:
        const boost::system::error_category& m_category;
    };

    static thread_local map<std::string, CategoryAdapter> nameToCategory;
    auto result = nameToCategory.emplace(error.category().name(), error.category());
    auto& category = result.first->second;
    return error_code(error.value(), category);
}

};

int main() {
    auto a = boost::system::errc::make_error_code(boost::system::errc::address_family_not_supported);
    auto b = std::make_error_code(a);
    std::cout << b.message() << std::endl;
}

2
从Boost版本1.65开始,您可以将boost :: error_code转换为其C ++ 11对应项
在C++11编译器上,Boost.System现在提供了从boost::system::error_category、error_code和error_condition到它们的标准等效项(在中)的隐式转换。
这使得库能够公开一个C++11接口,并通过std::error_code报告错误,即使使用Boost.System,直接或通过依赖项,如Boost.ASIO。
所以现在应该很简单:
auto ec = static_cast<std::error_code>(boost_error_code);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接