noncopyable允许程序轻松地实现一个禁止的类
noncopyable位于名字空间boost,需要包含头文件
或者
在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:默认构造;
编写代码的过程中有时会出现一些暂时用不到但又必须保留的变量, gcc等编译器会对此发出警告,使用“-wunused”可以关闭这些警告消息,不过这也有可能导致潜在的隐患。古老的办法是使用“(void) var”的形式来“使用”一下变量,但这种方法含义不明确,不利干维护
ignore_unused位于名字空间 boost,
需要包含头文件
#include
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
(); - }
在实际的软件开发过程中我们经常会遇到“无效值”的情况,例如函数并不是总能返回有效值,很多时候函数正确执行了,但结果却不是合理的值。如果用数学语言来解释,就是返回值位于函数解空间之外。
求一个数的倒数,在实数域内开平方,在字符串中查找子串,它们都可能返回“无效值”。有些无效返回的情况可以用抛出异常的方式来通知用户,但有的情况下这样代价很高或者不允许异常,这时必须要以某种合理的、高效的方式通知用户。
表示“无效值”最常用的做法是增加一个“哨兵”的角色,它位于解空间之外,如NULL、-1、EOF、string : : npos、vector ::end()等。但这些做法不够通用,而且很多时候不存在解空间之外的“哨兵”。另外一个方法是使用pair
optional使用“容器”语义,包装了“可能产生无效值”的对象,实现了“未初始化”的概念,为这种“无效值”的情形提供了一个更好的解决方案。它已经被收入C++17标准。
#include
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
- #include
- #include
- using namespace boost;
-
-
- int main()
- {
- optional<int>op; //未初始化
- optional<int>op1(none); //使用 none 相当于未初始化
- assert(!op);
- assert(op==op1);
-
- optional
ops("test"); - std::cout<<*ops<
-
- ops.emplace("monada",3);
- assert(*ops=="mon");
-
- vector<int>v(10);
- optional
int>&>opv(v); -
- opv->push_back(5);
-
-
- }
复杂操作:
- #include
- #include
- #include
- #include
- 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<
- }
- }
工厂函数:
- optional
make_optional(T const&v); - optional
make_optional(bool condition,T const &v); -
但make_optional ()无法推导出 T引用类型的 optional_对象,如果需要一个optional的对象就不能使用make_optional ()函数。
make_optional()也不支持emplace 的用法,可能存在值的拷贝代价。
- auto x= make_optional(5)
- assert(*x==5);
-
- auto y=make_optional
((*x>10),1.0);
assign
许多情况下我们都需要为容器初始化或者赋值,填入大量的数据,比如初始错误代码和错误信息,或者是一些测试用的数据。在C++98中标准容器仅提供了容纳这些数据的方法,但填充的步骤却是相当地麻烦,必须重复调用insert()或者push_back ()等成员函数,这正是boost.assign出现的理由
list_insert
摘要:
- #include
- 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
- #include
- #include
- #include
- #include
- #include
- using namespace boost::assign;
-
- template<class T>
- void print(T& a)
- {
- for(auto &x:a)
- {
- std::cout<
- }
- }
-
- void simple()
- {
- std::vector<int> v;
- push_back(v)(1)(2)(3)(4)(5);
- print(v);
- std::list
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
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
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
- #include
- #include
- 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
(e)< - std::cout<
what()< - e<<err_str("other info");
- throw;
- }
- }
- catch(my_exception& e)
- {
- std::cout<<get_error_info
(e)< - }
- }
程序首先定义了一个异常类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 ;
实例:
- void ex()
- {
- try
- {
- throw my_exception()<<errinfo_api_function("call api")<<errinfo_errno(101);
-
- }
- catch(boost::exception&e)
- {
- std::cout<<*get_error_info
(e); - std::cout<<*get_error_info
(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
(e)<< '\n'; - }
注意代码中 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抛出的,则可能相当多并且不是用户友好的,但对于程序开发者可以提供很好的诊断错误的信息。
- try
- {
- throw enable_error_info(my_err)
- <<errinfo_api_function("fopen()")
- <<errinfo_errno(101);
- }
- catch(boost::exception&e)
- {
- std::cout<<diagnostic_information(e)<
- }
- try
- {
- BOOST_THROW_EXCEPTION(std::logic_error("logic"));
-
- }
- catch(const boost::exception& e)
- {
- std::cout<<diagnostic_information(e)<
- }
-
- }
-
相关阅读:
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