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