C/C++编程:错误(error)处理和异常(exception)处理

1059 篇文章 286 订阅

C++标准库是怎么对待异常的?

C++标准程序库有两种方式:

  • 标准程序库中有一部分,例如std classes,支持具体的错误处理,他们检测所有可能发生的错误,并于错误发生时抛出异常
  • STLvalarrays等,效率重于安全,因此几乎不检验逻辑错误,并且只检测执行期(runtime)错误

ps:逻辑错误和运行期程序的区别:

  • 逻辑错误:可以被避免,基本上是你代码逻辑出错,其错误原因发生在程序内作用域内
  • 运行期异常:由一个位于程序作用域外的原因引发,比如资源不足

标准异常类(standard exception classes)

语言本身或者标准程序库所抛出的所有异常,都派生自基类exception。类层次结构如下:

在这里插入图片描述
在这里插入图片描述

带着可以分为三组:

  • 语言本身支持的异常
  • C++标准程序库发出的异常
  • 程序作用域之外的异常

所用头文件

#include <exception>
#include <new>
#include <typeinfo>
#include <ios>
#include <stdexcept>
#include <future>

针对[语言支持]而设计的异常类

此类异常用以支撑某些语言特性。所以,它们不是标准库的一部分,而是核心语言的一部分

如果以下操作失败,就会抛出这一类的异常:

  • 运行期间,当dynamic_cast失败时,抛出bad_cast异常
  • 运行期RTTI过程中,如果交给typeid操作符的实参是0或者空指针typeid操作符bad_typeid异常
  • bad_exception是用来处理非预取的异常的。它可以用函数unexpected()抛出,该函数会在某个函数抛出的异常不再异常明细内时被调用。然而注意,C++11其不再鼓励使用异常明细

针对[逻辑错误]而设计的异常类

这些异常总是派生自logic_error。所谓的[逻辑错误]就是应该在程序中避免的错误。C++标准程序库提供以下逻辑错误类别:

  • invalid_argument表示无效参数
  • length_error指出某些行为“可能超过了最大极限”。比如传入的字符串长度太长
  • out_of_range指出参数值“不在预期范围内”。比如vector超过索引范围
  • domain_error指出专业领域范围内的错误。
  • 自C++11其,future_error用来指出当使用非同步系统调用时发生的逻辑差错。注意,此范围内的运行期差错是一般由class system_error发出

针对[运行期错误]而设计的异常类

派生自runtime_error,用来指出“不在程序范围内,而且不容易回避”的事件

  • range_error:内部计算时发生区间错误
  • overflow_error:算术运算时发生上溢位
  • underflow_error:算术运算时发生下溢位
  • 自C++11开始,system_error用来指出因底层操作系统而发生的错误。C++标准库可能在并发环境中抛出这个异常
  • new操作失败,bad_alloc异常,除非用的是new的nothrow版本
  • 针对标准程序库的IO部分,提供一个特殊的异常类ios_base::failure。当数据流由于错误或由于到达文件尾端而发生状态改变时,就可能抛出这个异常

异常类的成员

what()、code():描述异常信息

是什么

对所有标准异常类,what可以用来获取“类型以外的附加信息”,它返回一个以null结尾的byte string

  class exception
  {
 	 public:
  			virtual const char*  what() const noexcept;
  }

使用示例

#include <iostream>
using namespace std;
int main(int argc,char *argv[]){
    try {
        throw std::runtime_error("Out of memory");
    } catch (runtime_error &e) {
        printf("%s\r\n", e.what());
    }
    return 0;
}

在这里插入图片描述

差错码 和 差错状态

是什么?

和它类似的是,有个叫做 差错码 和 差错状态的东西也可以获取错误详情。

  • 差错码(error code):用来封装errno值。比如errno.h中有很多系统差错编号,尝尝是一些枚举值
  • 错误状态(error condition)是一种“抽象的可以移植的差错描述”对象

针对异常,C++标准库有时候给出差错码,有时候给出差错对象。C++标准库分别为差错码和差错对象提供了两个不同的类型:class std::error_codestd std::error_condition,它们本质上都是枚举类。我们来看下它是怎么用的

怎么用?

int main(){
    std::error_condition edtion;
    if(edtion == std::errc::invalid_argument){
        
    }else if(edtion == std::future_errc::future_already_retrieved){

    }
    
    std::error_code ecode;
    if(ecode == std::io_errc::stream){
        
    }else if(ecode == std::errc::io_error){
        
    }
}

另外,所有异常类比如std::system_error提供了非虚成员函数code(),它会返回一个** std::error_code**对象

  class system_error : public std::runtime_error
  {
  public:
    virtual const char* what() const noexcept;
    const error_code& code() const noexcept ;
  };


  class future_error : public std::logic_error
  {
  public:
    virtual const char* what() const noexcept;
    const error_code& code() const noexcept ;
  };

std::error_code(本质是一个class)提供了成员函数,可以获得具体的错误细节

  struct error_code
  {
  public: 
    int  value() const noexcept;
    const error_category& category() const noexcept ;
    string  message() const ;
    explicit operator bool() const noexcept;
    error_condition  default_error_condition(int __i) const noexcept;
	.....
  };

为什么要这样设计呢?

  • 因为不同的程序库可以对不同的差错码使用相同的整数值。所以,每个差错都有一个分类和一个值。只有在某个分类之内,每个值才有其明确意义
  • message() 会有一个相应的信息,这个信息一般是what()为所有异常查出的信息的一部分
  • operator bool ()返回一个真假值,表示是否有个差错码被设置(0表示无错误)。当有异常被捕获时,这个成员函数返回true
  • default_error_condition()会返回相应的error_condition,而error_condition又提供了 category()、message()、operator bool()等
  struct error_condition
  {
        int  value() const noexcept;
	    const error_category& category() const noexcept ;
	    string  message() const ;
	    explicit operator bool() const noexcept;
	    ....
  };

  • category()会返回相应的error_category,而error_category又提供了如下接口:
    • name(),返回分类名称
    • message(int),返回传入值的信息
    • **default_error_condition(int __i) **:默认差错状态
    • 然后还有一些比较操作符
  /// error_category
  class error_category
  {
  public:
    virtual const char* name() const noexcept;
    virtual string message(int) const = 0;
    virtual error_condition  default_error_condition(int __i) const noexcept;
    bool operator<(const error_category& __other) const noexcept;
    bool operator==(const error_category& __other) const noexcept;
    bool operator!=(const error_category& __other) const noexcept;
  }

C++标准库提供了如下分类名称:
在这里插入图片描述
每一个分类都有响应的全局函数,返回分类:
在这里插入图片描述

因此,对于每一个差错码对象,我们都可以检查它是不是IO failure:

    ios_base::failure e("aa");
    if( e.code().category()  == std::iostream_category()){
        
    }

使用示例

template <typename T>
void processCodeException (const T& e)
{
    using namespace std;
    auto c = e.code();
    cerr << "- category:     " << c.category().name() << endl;
    cerr << "- value:        " << c.value() << endl;
    cerr << "- msg:          " << c.message() << endl;
    cerr << "- def category: "
         << c.default_error_condition().category().name() << endl;
    cerr << "- def value:    "
         << c.default_error_condition().value() << endl;
    cerr << "- def msg:      "
         << c.default_error_condition().message() << endl;
}

其他成员

其他成员,用的很少,只是分组生成、复制、赋值、销毁等操作

使用

抛出异常

抛出标准异常

理论

(1)可以抛出标准异常

  • 所有“提供了what()接口”的逻辑异常类和运行异常类,都只有一个构造函数接受std::string和一个构造函数接受**const char *,这些文件都将称为what()**的返回值。比如:
    在这里插入图片描述
  • std::system_error比较特别,它还提供的构造函数还包括一个差错码、一个what() string,和一个可有可无的分类等等
    在这里插入图片描述
  • 为了提供一个error_code对象,标准库引入了辅助函数make_error_code(…)

(2)不可以抛出基础的exception异常,也不可以抛出任何对语言支持而设计的异常,比如bad_cast、bad_typeid、bad_exception

使用

    throw std::out_of_range("std::out_of_range");
    throw std::system_error(std::make_error_code(std::errc::invalid_argument));
    throw std::system_error(std::make_error_code(std::errc::invalid_argument), "invalid_argument");

从标准异常类别(exception classes)中派生新异常[待补充]

还可以在程序中自定义一个异常类,这个类必须直接或者间接的继承exception,同时还必须确保其虚函数**what()和code()**可用

#include <iostream>
using namespace std;


class InterruptException : public std::runtime_error {
public:
    InterruptException() : std::runtime_error("InterruptException") {}

    ~InterruptException() {}
};



static double quotient()
{
    throw InterruptException(); // terminate function
    return 1.1;
}



int main(int argc,char *argv[]){
    try {
        double result = quotient();
        std::cout << "The quotient is: " << result << std::endl;
    } catch (InterruptException& divideByZeroException) {
        std::cout << "Exception occurred: " << divideByZeroException.what() << std::endl; // Exception occurred: attempted to divide by zero
    }
}

在这里插入图片描述

class exception_prt:延后处理异常

C++11起,可以将异常存储到类型为exception_prt的对象中,稍后处理

#include <exception>
#include <system_error>
#include <future>
#include <iostream>


std::exception_ptr expr;

void foo(){
    try {
        throw std::out_of_range("std::out_of_range");
    } catch (...) {
        expr  = std::current_exception();
    }
}

void bar(){
    if(expr != nullptr){
        std::rethrow_exception(expr);
    }
}

int main(){
    foo();
    bar();
}
  • std::current_exception():返回一个std::exception_ptr对象,执行当前正被处理的异常。该异常会保持有效,直到没有任何std::exception_ptr 指向它
  • std::rethrow_exception(expr):重新抛出异常

其他

构造函数、析构函数的异常处理

  • C++标准规定,构造函数失败,析构函数不会执行。就是说在构造函数抛出异常前分配的资源将无法释放。
    • 所以构造函数如果抛出异常前,申请了资源,需要自己释放。
  • C++标准指明析构函数不能、也不应该抛出异常
    • 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
    • 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

参考

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于C++11标准的多线程、同步机制、线程池和异步编程技术实现的多客户并发访问和模拟多个用户进行压力测试的服务端和客户端代码。其中,使用了Boost库的线程池实现。 服务端代码: ```cpp #include <iostream> #include <boost/asio.hpp> #include <boost/thread.hpp> #include <boost/bind.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/shared_ptr.hpp> #include <boost/asio/thread_pool.hpp> using boost::asio::ip::tcp; class session : public boost::enable_shared_from_this<session> { public: session(boost::asio::io_service& io_service) : socket_(io_service) { } tcp::socket& socket() { return socket_; } void start() { boost::asio::async_read(socket_, boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } void handle_read(const boost::system::error_code& error, size_t bytes_transferred) { if (!error) { boost::asio::async_write(socket_, boost::asio::buffer(data_, bytes_transferred), boost::bind(&session::handle_write, shared_from_this(), boost::asio::placeholders::error)); } else { std::cout << "Error: " << error.message() << std::endl; } } void handle_write(const boost::system::error_code& error) { if (!error) { boost::asio::async_read(socket_, boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else { std::cout << "Error: " << error.message() << std::endl; } } private: tcp::socket socket_; enum { max_length = 1024 }; char data_[max_length]; }; class server { public: server(boost::asio::io_service& io_service, short port) : io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)) { start_accept(); } void start_accept() { session_ptr new_session(new session(io_service_)); acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, boost::asio::placeholders::error)); } void handle_accept(session_ptr session, const boost::system::error_code& error) { if (!error) { session->start(); start_accept(); } else { std::cout << "Error: " << error.message() << std::endl; } } private: boost::asio::io_service& io_service_; tcp::acceptor acceptor_; }; int main(int argc, char* argv[]) { try { if (argc != 2) { std::cerr << "Usage: server <port>\n"; return 1; } boost::asio::io_service io_service; server s(io_service, std::atoi(argv[1])); boost::asio::thread_pool thread_pool(4); boost::asio::post(thread_pool, [&io_service]() { io_service.run(); }); boost::asio::post(thread_pool, [&io_service]() { io_service.run(); }); boost::asio::post(thread_pool, [&io_service]() { io_service.run(); }); boost::asio::post(thread_pool, [&io_service]() { io_service.run(); }); thread_pool.join(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; } ``` 客户端代码: ```cpp #include <iostream> #include <boost/asio.hpp> #include <boost/thread.hpp> #include <boost/bind.hpp> #include <boost/asio/thread_pool.hpp> using boost::asio::ip::tcp; class client { public: client(boost::asio::io_service& io_service, tcp::resolver::iterator endpoint_iterator) : io_service_(io_service), socket_(io_service) { boost::asio::async_connect(socket_, endpoint_iterator, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error)); } void handle_connect(const boost::system::error_code& error) { if (!error) { boost::asio::async_write(socket_, boost::asio::buffer(request_, request_length), boost::bind(&client::handle_write, this, boost::asio::placeholders::error)); } else { std::cout << "Error: " << error.message() << std::endl; } } void handle_write(const boost::system::error_code& error) { if (!error) { boost::asio::async_read(socket_, boost::asio::buffer(reply_, reply_length), boost::bind(&client::handle_read, this, boost::asio::placeholders::error)); } else { std::cout << "Error: " << error.message() << std::endl; } } void handle_read(const boost::system::error_code& error) { if (!error) { boost::asio::async_write(socket_, boost::asio::buffer(request_, request_length), boost::bind(&client::handle_write, this, boost::asio::placeholders::error)); } else { std::cout << "Error: " << error.message() << std::endl; } } private: boost::asio::io_service& io_service_; tcp::socket socket_; enum { request_length = 1024, reply_length = 8 }; char request_[request_length]; char reply_[reply_length]; }; int main(int argc, char* argv[]) { try { if (argc != 3) { std::cerr << "Usage: client <host> <port>\n"; return 1; } boost::asio::io_service io_service; tcp::resolver resolver(io_service); auto endpoint_iterator = resolver.resolve({ argv[1], argv[2] }); const int num_clients = 100; boost::asio::thread_pool thread_pool(num_clients); for (int i = 0; i < num_clients; ++i) { boost::asio::post(thread_pool, [&io_service, &endpoint_iterator]() { client c(io_service, endpoint_iterator); }); } thread_pool.join(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; } ``` 这里的服务端使用了异步I/O模型,每个客户端连接对应一个session对象,处理客户端请求时使用了异步读写操作。同时,为了提高并发处理能力,使用了Boost库的线程池实现多线程处理客户端请求。 客户端使用了异步I/O模型,每个客户端连接使用一个client对象,处理客户端请求时使用了异步读写操作。为了模拟多个用户进行压力测试,使用了Boost库的线程池实现多线程创建客户端连接。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值