• boost之实用工具


    noncopyable

    noncopyable允许程序轻松地实现一个禁止的类

    noncopyable位于名字空间boost,需要包含头文件

    或者utility.hpp>(后者包含了数个小工具的实现):

    原理

    在C++中定义一个类时,如果不明确定义拷贝构造函数和拷贝赋值操作符,编译器会为我们自动生成这两个函数

    实例:

    class empty_class{};

    这样一个简单的“空”类,编译器在处理时会“默默地”为它增加拷贝构造函数和拷贝赋值操作符,真实代码类似于:

    1. class empty_class
    2. {
    3. public:
    4. empty_class(const empty_class&){...};
    5. empty_class operator=(const empty_class&){...}
    6. }

    这是一个很经典的c++惯用法,原理很好理解,只需要私有化拷贝构造函数和拷贝赋值操乍符即可,手写代码也很简单(scoped_ptr就使用了这个惯用法),例如:

    1. class do_not_copy
    2. {
    3. private:
    4. do_not_copy(const do_not_copy&);
    5. void operator=(const do_not_copy&)
    6. }

    缺点:

    但如果程序中有大量这样的类,重复这样的代码是相当乏味的,而且代码出现的次数越多越容易增大手写出错的几率。虽然也可以用带参数的宏来减少重复,但解决方案不够优雅

    用法

    class do_not_copy:boost::nopcopyable//注意 使用默认的私有继承是允许的

    {...};

     如果有其他人误写了代码(很可能是没有仔细阅读接口文档),企图拷贝构造或者赋值这个对象,那么将不能通过编译器的审查:

    1. do_not_copy d1;
    2. do_not_copy d2(d1); //拷贝出错
    3. do_not_copy d3;
    4. d3=d1; //企图拷贝 出错

    实现:

    如果使用c++标准的default和 delete关键字,则noncopyable可以更清晰地实现如下:

    1. class noncopyable
    2. {
    3. protected
    4. noncopyable()=std::default;
    5. ~noncopyable()=std::default;
    6. private:
    7. noncopyable(const noncopyable&)=delete;
    8. const noncopyable& operator=(const noncopyable&)=delete;
    9. };

    delete:禁止;

    default:默认构造;

    ignore_unused

    编写代码的过程中有时会出现一些暂时用不到但又必须保留的变量, gcc等编译器会对此发出警告,使用“-wunused”可以关闭这些警告消息,不过这也有可能导致潜在的隐患。古老的办法是使用“(void) var”的形式来“使用”一下变量,但这种方法含义不明确,不利干维护

    ignore_unused位于名字空间 boost,

    需要包含头文件hpp>,即:

    #include

    using namespace boost;

    实现

    (1)变量不使用

    1. template<typename... Ts>
    2. inline void ignore_unused(Ts const& ...){}

    ignore_unused使用可变参数模板,可以支持任意数量、任意类型的变量,把它们作为函数的参数“使用”了一下,“骗”过了编译器,达到了与“(void)var”完全相同的效果。但它的命名更清晰,写法也更简单,而且由于是inline 函数,完全没有运行时的效率损失。

    基本用法:

    1. int main(int x,int y)
    2. {
    3. int i;
    4. ignore_unused(x,i);
    5. return y;
    6. }

    (2)类型不使用

    1. template<typename ... Ts>
    2. inline void ignore_unused(){}

    ignore unused的模板用法与函数用法类似,但它不需要函数参数,而是在模板参数列表里写出要忽略的类型。

    1. void func2()
    2. {
    3. typedef int result_type;
    4. ignore_unused();
    5. }

    optional

    在实际的软件开发过程中我们经常会遇到“无效值”的情况,例如函数并不是总能返回有效值,很多时候函数正确执行了,但结果却不是合理的值。如果用数学语言来解释,就是返回值位于函数解空间之外。

    求一个数的倒数,在实数域内开平方,在字符串中查找子串,它们都可能返回“无效值”。有些无效返回的情况可以用抛出异常的方式来通知用户,但有的情况下这样代价很高或者不允许异常,这时必须要以某种合理的、高效的方式通知用户。

    表示“无效值”最常用的做法是增加一个“哨兵”的角色,它位于解空间之外,如NULL、-1、EOF、string : : npos、vector ::end()等。但这些做法不够通用,而且很多时候不存在解空间之外的“哨兵”。另外一个方法是使用pair的方式,用一个额外的 bool值来标记值是否有效,标准容器set的 insert函数就是如此。

    optional使用“容器”语义,包装了“可能产生无效值”的对象,实现了“未初始化”的概念,为这种“无效值”的情形提供了一个更好的解决方案。它已经被收入C++17标准。

    #include

    using namespace  boost;

    实现

    1. class none_t();
    2. const none_t none=...;
    3. template<class T>
    4. class optional
    5. {
    6. public:
    7. optional();
    8. optional(none_t);
    9. optional(T const& v);
    10. optional(bool condition, T v);
    11. optional& operator=(T const& rhs);
    12. template<class... Args>
    13. void emplace(Args... &&args);
    14. ...
    15. }

    optional库首先定义了常量boost : :none,表示未初始化,明确了“无效值”;

    C++17标准中的optional 接口与boost.optional略有不同,使用std: :nullopt_t取代boost: :none t,并且用std::in_place_t支持在构造函数里就地创建对象。

    操作函数:

    构造函数:

    (1)无参的 optional()或者 optional(boost : :none)构造一个未初始化 optional对象;

     (2)   optional ( v)构造一个已初始化的optional对象,内部拷贝v的值。如果模板类型为T&,那么optional内部持有对引用的包装;

     (3)optional(condition, v)根据条件condition来构造optional对象,如果条件成立(true)则初始化为v,否则为未初始化;

    (4)optional支持拷贝构造和赋值操作,可以从另一个 optional对象构造;

    (5)emplace ( )是一个特殊的“赋值”函数,可以使用参数就地创建对象,避免了构造后再拷贝的代价。

    (6)想让一个optional对象重新恢复到未初始化状态可以向对象赋none值。

    optional采用了指针语义来访问内部保存的元素,这使得 optional未初始化时的行为就像一个空指针,可以使用operator bool()和 operator! ()来检测是否有效。

    optional另外提供三个value ()系列成员函数,它们比 operator*和 operator->更加安全:

    value ()同样可以访问元素,但如果 optional未初始化会抛出bad_optional_access异常;

    value_or(default)可以保证返回一个有效的值,如果 optional已初始化,那么返回内部的元素,否则返回default;

    value_or_eval(f)类似value_or (),但它的参数是一个可调用的函数或者函数对象,如果 optional未初始化则返回f的执行结果即f()。

    实例:

    1. #include
    2. #include
    3. #include
    4. using namespace boost;
    5. int main()
    6. {
    7. optional<int>op; //未初始化
    8. optional<int>op1(none); //使用 none 相当于未初始化
    9. assert(!op);
    10. assert(op==op1);
    11. optionalops("test");
    12. std::cout<<*ops<
    13. ops.emplace("monada",3);
    14. assert(*ops=="mon");
    15. vector<int>v(10);
    16. optionalint>&>opv(v);
    17. opv->push_back(5);
    18. }

    复杂操作:

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace boost;
    6. optional<double>clac(int x)
    7. {
    8. return optional<double>(x!=0,1.0/x);
    9. }
    10. optional<double>sqrt_op(double x)
    11. {
    12. return optional<double>(x>=0,sqrt(x));
    13. }
    14. int main()
    15. {
    16. optional<double>d=clac(13);
    17. if(d)
    18. {
    19. std::cout<<*d<
    20. }
    21. }

    工厂函数:

    1. optionalmake_optional(T const&v);
    2. optionalmake_optional(bool condition,T const &v);

    但make_optional ()无法推导出 T引用类型的 optional_对象,如果需要一个optional的对象就不能使用make_optional ()函数。
    make_optional()也不支持emplace 的用法,可能存在值的拷贝代价。

    1. auto x= make_optional(5)
    2. assert(*x==5);
    3. auto y=make_optional((*x>10),1.0);

    assign


    许多情况下我们都需要为容器初始化或者赋值,填入大量的数据,比如初始错误代码和错误信息,或者是一些测试用的数据。在C++98中标准容器仅提供了容纳这些数据的方法,但填充的步骤却是相当地麻烦,必须重复调用insert()或者push_back ()等成员函数,这正是boost.assign出现的理由

    list_insert

    摘要:

    1. #include
    2. template<class T>
    3. class list_inserter
    4. {
    5. public:
    6. list_inserter& operator,(const T&r);
    7. list_inserter& operator()();
    8. list_inserter&()(const T&r);
    9. public:
    10. ....
    11. }

    list_inserter内部存储一个函数对象insert_用来操作容器,这个函数对象包装了容器的push_back. push_front等操作,例如:

    1. list_insert& operator,(const  T&r)
    2. {
    3. insert_(r);
    4. return *this;
    5. }

    operator+=

    1. using namespace boost::assign;
    2. vector<int>v;
    3. v+=1,2,3,4,5,6*6;

    operator()

    operator+=仅作用于标准容器,而且在处理map容器时也显得有些麻烦,所以我们可以直接使用工厂函数 insert ( ) /push_front () /push _back (),利用它们返回的 listinserter对象来填入数据。

    示范工厂函数insert ( )、push_front ()、push_back()用法的代码如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace boost::assign;
    8. template<class T>
    9. void print(T& a)
    10. {
    11. for(auto &x:a)
    12. {
    13. std::cout<
    14. }
    15. }
    16. void simple()
    17. {
    18. std::vector<int> v;
    19. push_back(v)(1)(2)(3)(4)(5);
    20. print(v);
    21. std::listl;
    22. push_front(l)("c")("cpp")("lua")("swift");
    23. std::map<int,std::string>m;
    24. insert(m)(1,"one")(2,"tow");
    25. }
    26. int main()
    27. {
    28. simple();
    29. }

    其余参考c++新特性std::initializer_list

    exception

    异常是c++错误处理的重要机制,它改变了传统的使用错误返回值的处理模式,简化了函数的接口和调用代码,有助于编写整洁、优雅、健壮的程序。c++标准定义了标准异常类std::exception及一系列子类,是整个C++语言错误处理的基础。

    boost.exception库针对标准库中异常类的缺陷进行了强化,提供<<操作符重载,可以向异常传入任意数据,有助于增加异常的信息和表达力,其中的部分特性已经被收入C++标准。

    #include

    using namespace boost;

    标准库的异常

    C++标准中定义了一个异常基类std: :exception和 try/catch/throw异常处理机制,std: :exception又派生出若干子类,用以描述不同种类的异常,如 bad_alloc, badcast、 out_of_range等,共同构建了C++异常处理框架。

    C++允许任何类型作为异常抛出,但在std::exception出现后,我们应该尽量使用它,因为std::exception提供了一个很有用的成员函数what ( ) ,可以返回异常所携带的信息,这比简单地抛出一个整数错误值或者字符串更好更安全。

    1. class my_exception:public std::logic_error //继承标准异常类
    2. {
    3. private:
    4. int err_no;
    5. public:
    6. my_exception(const char* msg,int err);
    7. std::logic_err(msg),err_no(err){}
    8. int get_err_no()
    9. {
    10. return err_no;
    11. }
    12. }

    而且这种解法还存在一个问题:很多时候当发生异常时不能获得有关异常的完全诊断信息,而标准库的异常类一旦被抛出,它就成为了一个“死”对象,程序失去了对它的控制能力,只能使用它或者再抛出一个新的异常。

    boost库的异常

    exception()

    1. class exception
    2. {
    3. protected:
    4. exception();
    5. exception(exception const&x);
    6. virtual~exception();
    7. private:
    8. template<typename T,typename Tag,typename E>
    9. friend E const&operator<<(E const&,error_infoconst&);
    10. };
    11. typename value_type* get_error_inf(E& x);

    exception类几乎没有公开的成员函数(但有大量用于内部实现的私有函数和变量),被保护的构造函数表明了它的设计意图:它是一个抽象类,除了它的子类,任何人都不能创建或者销毁它,这保证了exception不会被误用。

    exception的重要能力在于其友元操作符<<,可以存储error_info对象的信息,存入的信息可以用自由函数get_error_info()随时再取出来。这个函数返回一个存储数据的指针,如果exception里没有这种类型的信息则返回空指针。

    exception特意没有从std::exception继承,因为现实中已存的大量代码已经有很多std: :exception 的子类,而如果 exception也是 std::exception的子类,则对exception再使用继承可能会引发“钻石型”多重继承问题。

    error_info

    1. template<class Tag,class T>
    2. class error_info
    3. {
    4. public:
    5. typedef T value_type();
    6. error_info(value_type const& T);
    7. value_type&value();
    8. }

    error_info提供了向异常类型添加信息的通用解法。第一个模板类型参数Tag是一个标记,它通常(最好)是一个空类,仅用来标记error_info类,使它在模板实例化时生成不同的类型。第二个模板类型参数T是真正存储的信息数据,可以用成员函数value ()访问。

    向异常传递信息

    因为exception被定义为抽象类,因此我们的程序必须定义它的子类才能使用它,如前所述,exception必须使用虚继承的方式。通常,继承完成后自定义异常类的实现也就结束了,不需要“画蛇添足”地向它增加成员变量或者成员函数,这些工作都已经由exception完成了。例如:

    1. struct my_exception:virtual std::exception,virtual boost::exception
    2. {
    3. };

    实例:

    1. #include
    2. #include
    3. #include
    4. using namespace boost;
    5. struct my_exception:virtual std::exception,virtual boost::exception
    6. {
    7. };
    8. typedef boost::error_info<struct tag_err_no,int> err_no;
    9. typedef boost::error_info<struct tag_err_str,std::string> err_str;
    10. int main()
    11. {
    12. try
    13. {
    14. try{
    15. throw my_exception()<<err_no(10);
    16. }
    17. catch(my_exception&e)
    18. {
    19. std::cout<<*get_error_info(e)<
    20. std::cout<what()<
    21. e<<err_str("other info");
    22. throw;
    23. }
    24. }
    25. catch(my_exception& e)
    26. {
    27. std::cout<<get_error_info(e)<
    28. }
    29. }

    程序首先定义了一个异常类my_exception,然后使用typedef定义了两种异常信息:err_no和 err_str,用int 和 string 分别存储错误码和错误信息。main ()函数使用function-try块来捕获异常,它把整个函数体都包含在try块中,可以更好地把异常处理代码与正常流程代码分离。

    throw my_exception()语句创建了一个my_exception异常类的临时对象,并立刻使用<<向它传递了err_no对象,存入错误码10。随后,异常被catch 块捕获,自由函数get_error_info (e)可以获得异常内部保存的信息值的指针,所以需要用解引用操作符*访问。

    错误信息类

    如下图:

    原型最终类型
    typedef error_info<...>errinfo_api_function;
    typedef error_info<...>errinfo_at_line;
    typedef error_info<...>errinfo_errno;
    typedef error_info<...>errinfo_file_handle;errinfo_file_name ;
    typedef error_info<...>errinfo_file_name ;
    typedef error_info<...>errinfo_file_open_mode;
    typedef error_info<...>errinfo_type_info_name ;

    实例:

    1. void ex()
    2. {
    3. try
    4. {
    5. throw my_exception()<<errinfo_api_function("call api")<<errinfo_errno(101);
    6. }
    7. catch(boost::exception&e)
    8. {
    9. std::cout<<*get_error_info(e);
    10. std::cout<<*get_error_info(e);
    11. }
    12. }

    包装标准异常

    exception库提供一个模板函数enable_error_info(T &e),其中T是标准异常类或者其他自定义类型。它可以包装类型T,产生一个从boost ::exception和T派生的类,从而在不修改原异常处理体系的前提下获得 boost:: exception的所有好处。如果类型丁已经是boost : :exception的子类,那么enable_error_info将返回e的一个拷贝。

    enable_error_info()通常用在程序中已经存在异常类的场合,对这些异常类的修改很困难甚至不可能(比如已经编译成库)。这时候enable_error_info ()就可以包装原有的异常类,从而很容易地在不变动任何已有代码的基础上把 boost ::exception集成到原有异常体系中

    1. struct my_err{};
    2. try
    3. {
    4. throw enable_error_info(my_err())<<errinfo_errno(10);
    5. }
    6. catch(const boost::exception& e)
    7. {
    8. std::cout << *get_error_info(e)<< '\n';
    9. }

    注意代码中 catch 的用法,enable_error_info ( )返回的对象是boost: :exception和 my_err的子类,catch 的参数可以是这两者中的任意一个,但如果要使用boost : :exception所存储的信息,就必须用boost ::exception来捕获异常。

    使用函数抛出异常

    exception库在头文件里提供throw_exception ()函数来简化enable_error_info ()的调用,它可以代替原始的throw语句来抛出异常,会自动使用enable_error_info()来包装异常对象,而且支持线程安全,比直接使用throw更好,相当于:

    throw(boost::enable_error_inf(e));

    从而确保抛出的异常是boost ::exception的子类,可以追加异常信息。例如:

    throw_exception(std::runtime_error("runtime"));

    在throw_exception ()的基础上exception 库又提供了一个非常有用的宏 BOOST_THROW_EXCEPTION,它调用了boost : :throw_exception ()和enable_error_info() ,因而可以接受任意的异常类型,同时又使用throw_function、throw_file和 throw_line自动向异常添加了发生异常的函数名、文件名和行号等信息。

    获得更多的信息

    diagnostic_information ()可以输出异常包含的所有信息,如果异常是由宏 BO0ST_THROW EXCEPTION抛出的,则可能相当多并且不是用户友好的,但对于程序开发者可以提供很好的诊断错误的信息。

    1. try
    2. {
    3. throw enable_error_info(my_err)
    4. <<errinfo_api_function("fopen()")
    5. <<errinfo_errno(101);
    6. }
    7. catch(boost::exception&e)
    8. {
    9. std::cout<<diagnostic_information(e)<
    10. }
    11. try
    12. {
    13. BOOST_THROW_EXCEPTION(std::logic_error("logic"));
    14. }
    15. catch(const boost::exception& e)
    16. {
    17. std::cout<<diagnostic_information(e)<
    18. }
    19. }

  • 相关阅读:
    AKKA.Net 的使用 来自CHATGPT
    echarts 双向柱状图 共用y轴
    python正则表达(06)
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java成绩管理与学情分析系统11yzf
    Google Earth Engine(GEE)——不同决策树数量分类精度对比分析(随机森林分类为例)
    npm run dev和npm run serve两个命令的区别
    PPT+Visio复现顶刊三维流程图
    【1】初识 Python
    C++单例写法记录
    Vue2生命周期详解
  • 原文地址:https://blog.csdn.net/qq_62309585/article/details/126845051