C++11 std::error_code错误码封装

本文分析了C++中的异常处理和错误码的优缺点,介绍了std::error_code的出现,强调了它在性能、结构化、可扩展性和线程安全性上的优势,以及如何在实际项目中使用和封装std::error_code。
摘要由CSDN通过智能技术生成

C++11 std::error_code错误码封装

概要

在C++中,异常处理和错误码是两种常见的错误处理机制,然而它们都带有一些潜在的缺点。为了更优雅地处理错误,C++11引入了std::error_code,为我们提供了一种更为灵活、轻量级的错误处理方式。

异常处理,尽管是强大而直观的机制,但其引入的运行时开销可能对性能造成一定影响。此外,异常处理通常用于处理真正的异常情况,而不是普通的错误。在某些场景下,这种机制可能显得过于沉重,尤其是对于一些性能敏感的应用。

错误码,使用宏定义或枚举来表示错误,是一种轻量级的错误处理方式。然而,它们可能变得冗长且难以维护,而且在多线程环境下需要谨慎处理,以防止共享数据的竞态条件。

此外,std::error_code 可以轻松与 POSIX 的 errno 进行转换,使得在C++标准库和POSIX函数之间进行交互变得异常简单。这种互操作性增强了代码的灵活性和可维护性,使得错误处理更加优雅和符合现代C++的设计理念。

错误码的缺点

#define CV_ERR_INVALID_IMAGE_FORMAT 0x00B0
#define CV_ERR_UNSUPPORTED_OPERATION 0x00B1
#define CV_ERR_INSUFFICIENT_MEMORY 0x00B2
#define CV_ERR_TIMEOUT 0x00B3
#define CV_ERR_INVALID_PARAMETER 0x00B4
#define CV_ERR_IO_ERROR 0x00B5
#define CV_ERR_NOT_FOUND 0x00B6
#define CV_ERR_DIVISION_BY_ZERO 0x00B7
#define CV_ERR_OVERFLOW 0x00B8
#define CV_ERR_UNDERFLOW 0x00B9
  • 缺乏结构: 使用宏或枚举定义的错误码表通常缺乏结构,难以组织和分类。随着项目规模的增大。
  • 不具备上下文信息: 宏定义的错误码通常只是一串数字,缺乏对错误的详细描述和上下文信息。这使得在代码中理解错误的含义变得更为困难。
  • 不容易扩展: 添加新的错误码可能需要修改多个文件,容易引入错误和破坏代码的一致性。这对于维护大型项目时可能会带来挑战。
  • 不便于集中管理: 错误码分散在不同的头文件中,可能不方便集中管理和查找。在大型项目中,可能需要额外的文档来说明各个错误码的含义。

将错误码放在同一个文件中时错误码文件可能变得庞大,导致文件膨胀,导致该头文件维护起来异常困难。若放在不同头文件中则会导致开发人员需要在多个文件中查找错误码,可能降低代码的可读性和维护性,并且分散的错误码可能导致不同的团队采用不同的规范,使得错误码的一致性难以保证。

异常的缺点

  • 性能开销: 异常处理可能引入一定的性能开销。当异常被抛出时,系统需要回溯堆栈,查找异常处理的地点,这可能涉及昂贵的运行时开销。
  • 难以调试:异常处理使得代码的调试变得更加困难。在异常抛出的地方很难立即查看错误的具体原因,而需要回溯堆栈来定位异常的起源。这增加了调试的复杂性。
  • 控制流不明确:异常会改变程序的正常控制流,这可能使得代码的控制流不够明确。异常通常用于处理非常规的错误情况,但过度使用异常可能导致代码难以理解。
#include <iostream>
#include <stdexcept>

void func(int num) {
    if (input < 0) {
        throw std::invalid_argument("Input should be non-negative.");
    }
    
    std::cout << "Processing input: " << num << std::endl;
}

int main() {
    try {
        int num;
        std::cout << "Enter a number: ";
        std::cin >> num;

        func(num);

        // 这里的代码在异常被捕获后是否执行是不确定的
        std::cout << "Continuing with normal execution." << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;

        // 异常被捕获后,程序继续执行这里的代码
        std::cout << "Continuing after exception handling." << std::endl;
    }

    // 其他业务逻辑

    return 0;
}

···

std::error_code

std::error_code 是一种轻量级的错误处理机制。它不涉及异常处理的运行时开销,因此在性能上通常更高效,并且可以将错误码分散到多个文件还不需要维护错误码的真实值保持唯一。

  • 结构化和分类: std::error_code允许错误码进行结构化和分类。通过使用错误类别和错误码值,可以更清晰地组织和理解错误信息,而不像宏定义的错误码那样显得零散。
  • 可扩展性:添加新的错误码可以通过定义新的错误类别或扩展已有的错误类别来实现,而不需要修改现有的代码。这提高了可扩展性,避免了多次修改头文件的问题。
  • 可移植性: std::error_code 提供了与平台相关的错误码的抽象,这使得代码更容易在不同平台上移植而无需大量修改。
  • 线程安全:std::error_code 的设计考虑到了多线程的情况。可以在多个线程之间安全地传递和共享std::error_code,而不需要额外的同步措施。
  • 不改变控制流: 使用 std::error_code不改变程序的正常控制流,因此代码在错误和正常情况下的行为更为一致,避免了控制流不明确的问题。

一个简单的std::error_code封装

#ifndef CQSS_BASE_ERRC_HPP_
#define CQSS_BASE_ERRC_HPP_

#include <system_error>
#include <unordered_map>

#define NO_ERR std::make_error_code(cqss::base::errc::Errc::kNoErr)
#define ERR(ec) std::make_error_code(ec)

namespace cqss {
namespace base {
namespace errc {

enum class Errc : int { kNoErr = 0, kErr };

const std::unordered_map<Errc, std::string> ErrMsg{
    {Errc::kNoErr, "Successful"},
    {Errc::kErr, "UnSuccessful"},
};

class ErrorCategory : public std::error_category {
 public:
  inline const char* name() const noexcept override {
    return "cqss error category";
  }

  inline std::string message(int ec) const override {
    auto errc = static_cast<Errc>(ec);
    if (ErrMsg.find(errc) != ErrMsg.end()) {
      return ErrMsg.find(errc)->second;
    }
    return "Unknown error";
  }

  inline std::string message(Errc errc) const {
    if (ErrMsg.find(errc) != ErrMsg.end()) {
      return ErrMsg.find(errc)->second;
    }
    return "Unknown error";
  }
};
}  // namespace errc
}  // namespace base
}  // namespace cqss

namespace std {
const cqss::base::errc::ErrorCategory cqssErrorCategory{};
inline std::error_code make_error_code(cqss::base::errc::Errc ec) {
  return {static_cast<int>(ec), cqssErrorCategory};
}
}  // namespace std

#endif  // CQSS_BASE_ERRC_HPP_

具体使用的例子

std::error_code Server::Init(ServConfig &cfg) {
  error_code ec = NO_ERR;

  if ((fd_ = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    clog << format("{}: create socket error", kUnit);
    // 也可抛出自定义异常
    return ERR(Errc::kErr);
  }
  strncpy(ip_, cfg.getIP().data(), net::kIPv4Len);
  port_ = cfg.getPort();
  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(ip_);
  addr.sin_port = htons(port_);
  if (-1 == bind(fd_, reinterpret_cast<sockaddr *>(&addr), sizeof(sockaddr))) {
    clog << format("{}: {}: bind error\n", kUnit, fd_);
    // 可兼容errno
    return ERR(static_cast<std::errc>(errno));
  }
  listenQueueLen_ = cfg.getListenQueueLen();
  rdBuffLen_ = cfg.getRdBuffLen();
  wtBuffLen_ = cfg.getWtBuffLen();
  return ec;
}
  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`std::error_code`是C++11标准中定义的一个类型,用于表示错误码。它通常与`std::error_category`一起使用,后者定义了一组错误码的类别。使用`std::error_code`和`std::error_category`可以方便地表示和处理各种错误类型。 下面是一个简单的例子: ```cpp #include <system_error> #include <iostream> enum class my_error_code { ok = 0, file_not_found, invalid_argument }; namespace std { template <> struct is_error_code_enum<my_error_code> : true_type {}; } std::error_code make_error_code(my_error_code e) { static const std::error_category& category = []() -> const std::error_category& { static std::error_category instance{"my_error_category"}; return instance; }(); return {static_cast<int>(e), category}; } int main() { std::error_code ec = my_error_code::file_not_found; std::cout << ec.message() << std::endl; return 0; } ``` 在上面的例子中,我们首先定义了一个枚举类型`my_error_code`,表示三种错误码。然后我们通过特化`std::is_error_code_enum`模板,告诉编译器`my_error_code`是一个`std::error_code`可以处理的错误码类型。接着定义了一个`make_error_code`函数,用于将枚举类型转换为`std::error_code`对象。在`make_error_code`函数中,我们定义了一个静态变量表示错误码的类别,然后返回一个由错误码和类别构成的`std::error_code`对象。 在`main`函数中,我们首先将枚举类型转换为`std::error_code`对象,然后调用`message`方法输出错误消息。 以上是一个简单的使用`std::error_code`的例子,通过了解`std::error_code`的用法,我们可以更好地处理各种错误类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值