boost之实用工具

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 boost之实用工具,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

noncopyable

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

noncopyable位于名字空间boost,需要包含头文件<boost/noncopyable.hpp>

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

原理

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

实例:

class empty_class{};

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

class empty_class

{

public:

empty_class(const empty_class&){...};

empty_class operator=(const empty_class&){...}

}

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

class do_not_copy

{

private:

do_not_copy(const do_not_copy&);

void operator=(const do_not_copy&)

}

缺点:

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

用法

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

{…};

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

do_not_copy d1;        
do_not_copy d2(d1);       //拷贝出错
do_not_copy d3;
d3=d1;                   //企图拷贝 出错

实现:

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


class noncopyable
{
protected
  noncopyable()=std::default;
  ~noncopyable()=std::default;
private:

  noncopyable(const noncopyable&)=delete;
  const noncopyable& operator=(const noncopyable&)=delete;
  
};

delete:禁止;

default:默认构造;

ignore_unused

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

ignore_unused位于名字空间 boost,

需要包含头文件<boost/core/ignoreunused.hpp>,即:

#include <boost/core/ignore_unused.hpp>

using namespace boost;

实现

(1)变量不使用

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

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

基本用法:

int main(int x,int y)
{
  int i;
  ignore_unused(x,i);
  return y;
}

(2)类型不使用

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

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

void func2()
{
  typedef int result_type;
  ignore_unused<result_type>();
}

optional

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

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

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

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

#include<boost/optional.hpp>

using namespace  boost;

实现

class none_t();
const none_t none=...;

template<class T>
class optional
{
    public:
    optional();
    optional(none_t);
    optional(T const& v);
    optional(bool condition, T v);
    
    optional& operator=(T const& rhs);
    template<class... Args>
    void emplace(Args... &&args);

    ...
}

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()。

实例:

#include<boost/optional.hpp>
#include<iostream>
#include<vector>
using namespace boost;


int main()
{
    optional<int>op;               //未初始化
    optional<int>op1(none);           //使用 none 相当于未初始化
    assert(!op);
    assert(op==op1);

    optional<string>ops("test");
    std::cout<<*ops<<std::endl;

    ops.emplace("monada",3);
    assert(*ops=="mon");

    vector<int>v(10);
    optional<vector<int>&>opv(v);

    opv->push_back(5);


}

复杂操作:

#include<boost/optional.hpp>
#include<iostream>
#include<vector>
#include<math.h>
using namespace boost;
 optional<double>clac(int x)
  {
    return optional<double>(x!=0,1.0/x);
  }
  optional<double>sqrt_op(double x)
  {
    return optional<double>(x>=0,sqrt(x));
  }
int main()
{
  
  optional<double>d=clac(13);
  if(d)
  {
    std::cout<<*d<<std::endl;
  }
}

工厂函数:

optional<T>make_optional(T const&v);
optional<T>make_optional(bool condition,T const &v);

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

auto x= make_optional(5)
assert(*x==5);

auto y=make_optional<duoble>((*x>10),1.0);

assign

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

list_insert

摘要:

#include<boost/assign.hpp>
template<class T>
class list_inserter
{
    public:
    list_inserter& operator,(const T&r);
    list_inserter& operator()();
    list_inserter&()(const T&r);
    public:
    ....
}

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

list_insert& operator,(const  T&r)

{

insert_(r);

return *this;

}

operator+=

using namespace boost::assign;

vector<int>v;
v+=1,2,3,4,5,6*6;

operator()

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

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

#include<boost/assign.hpp>
#include<iostream>
#include<map>
#include<vector>
#include<list>
#include<string>
using namespace boost::assign;

template<class T>
void print(T& a)
{
    for(auto &x:a)
    {
        std::cout<<x<<std::endl;
    }
} 

void simple()
{
    std::vector<int> v;
    push_back(v)(1)(2)(3)(4)(5);
    print(v);
    std::list<std::string>l;
    push_front(l)("c")("cpp")("lua")("swift");
    std::map<int,std::string>m;
    insert(m)(1,"one")(2,"tow");


}
int main()
{
    simple();
}

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

exception

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

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

#include<boost/exception/all.hpp>

using namespace boost;

标准库的异常

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

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

class my_exception:public std::logic_error //继承标准异常类
{
    private:
    int err_no;
    public:
    my_exception(const char* msg,int err);
    std::logic_err(msg),err_no(err){}
    int get_err_no()
    {
        return err_no;
    }
}

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

boost库的异常

exception()

class exception
{
    protected:
    exception();
    exception(exception const&x);
    virtual~exception();
    private:
    template<typename T,typename Tag,typename E>
    friend E const&operator<<(E const&,error_info<Tag,T>const&);
};
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

template<class Tag,class T>
class error_info
{
    public:
    typedef T value_type();
    error_info(value_type const& T);
    value_type&value();
}

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

向异常传递信息

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

struct my_exception:virtual std::exception,virtual boost::exception
{
};

实例:

#include<boost/exception/all.hpp>
#include<string>
#include<iostream>
using namespace boost;

struct my_exception:virtual std::exception,virtual boost::exception
{
};

typedef boost::error_info<struct tag_err_no,int> err_no;
typedef boost::error_info<struct tag_err_str,std::string> err_str;

int main()
{
try
{
       try{
        throw my_exception()<<err_no(10);
       }
       catch(my_exception&e)
       {
        std::cout<<*get_error_info<err_no>(e)<<std::endl;
        std::cout<<e.what()<<std::endl;
        e<<err_str("other info");
        throw;
       }
}
catch(my_exception& e)
{
   std::cout<<get_error_info<err_str>(e)<<std::endl;
}
}

程序首先定义了一个异常类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 <err_no>(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 ;

实例:

void ex()
{
    try
    {
        throw my_exception()<<errinfo_api_function("call api")<<errinfo_errno(101);

    }
    catch(boost::exception&e)
    {
        std::cout<<*get_error_info<errinfo_api_function>(e);
        std::cout<<*get_error_info<errinfo_errno>(e);
    }
}

包装标准异常

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集成到原有异常体系中


struct my_err{};
    try
    {
        throw enable_error_info(my_err())<<errinfo_errno(10);

    }

    catch(const boost::exception& e)
    {
        std::cout << *get_error_info<errinfo_errno>(e)<< '\n';
    }

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

使用函数抛出异常

exception库在头文件<boost/throw_exception.hpp>里提供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抛出的,则可能相当多并且不是用户友好的,但对于程序开发者可以提供很好的诊断错误的信息。

 try
    {
        throw enable_error_info(my_err)
        <<errinfo_api_function("fopen()")
        <<errinfo_errno(101);
    }
    catch(boost::exception&e)
    {
        std::cout<<diagnostic_information(e)<<std::endl;
    }
    try
    {
                    BOOST_THROW_EXCEPTION(std::logic_error("logic"));
        
    }
    catch(const boost::exception& e)
    {
       std::cout<<diagnostic_information(e)<<std::endl;
    }
    
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129674.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!