Effective C++ 笔记 —— Item 29: Strive for exception-safe code.
Suppose we have a class for representing GUI menus with background images. The class is designed to be used in a threaded environment, so it has a mutex for concurrency control:
class PrettyMenu { public: // ... void changeBackground(std::istream& imgSrc); // change background image // ... private: Mutex mutex; // mutex for this object Image *bgImage; // current background image int imageChanges; // # of times image has been changed };
Consider this possible implementation of PrettyMenu‘s changeBackground function:
void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); // acquire mutex (as in Item 14) delete bgImage; // get rid of old background ++imageChanges; // update image change count bgImage = new Image(imgSrc); // install new background unlock(&mutex); // release mutex }
From the perspective of exception safety, this function is about as bad as it gets. There are two requirements for exception safety, and this satisfies neither.When an exception is thrown, exception-safe functions:
- Leak no resources. The code above fails this test, because if the "new Image(imgSrc)" expression yields an exception, the call to unlock never gets executed, and the mutex is held forever.
- Don’t allow data structures to become corrupted. If "new Image(imgSrc)" throws, bgImage is left pointing to a deleted object. In addition, imageChanges has been incremented, even though it’s not true that a new image has been installed. (On the other hand, the old image has definitely been eliminated, so I suppose you could argue that the image has been "changed.")
Exception-safe functions offer one of three guarantees:
- Functions offering the basic guarantee promise that if an exception is thrown, everything in the program remains in a valid state. No objects or data structures become corrupted, and all objects are in an internally consistent state (e.g., all class invariants are satisfied). However, the exact state of the program may not be predictable. For example, we could write changeBackground so that if an exception were thrown, the PrettyMenu object might continue to have the old background image, or it might have some default background image, but clients wouldn’t be able to predict which. (To find out, they’d presumably have to call some member function that would tell them what the current background image was.)
- Functions offering the strong guarantee promise that if an exception is thrown, the state of the program is unchanged. Calls to such functions are atomic in the sense that if they succeed, they succeed completely, and if they fail, the program state is as if they’d never been called.
- Functions offering the nothrow guarantee promise never to throw exceptions, because they always do what they promise to do. All operations on built-in types (e.g., ints, pointers, etc.) are nothrow (i.e., offer the nothrow guarantee). This is a critical building block of exception-safe code.
Exception-safe code must offer one of the three guarantees above. If it doesn’t, it’s not exception-safe. The choice, then, is to determine which guarantee to offer for each of the functions you write.
As a general rule, you want to offer the strongest guarantee that’s practical. From an exception safety point of view, nothrow functions are wonderful, but it’s hard to climb out of the C part of C++ without calling functions that might throw. Anything using dynamically allocated memory (e.g., all STL containers) typically throws a bad_alloc exception if it can’t find enough memory to satisfy a request (see Item 49). Offer the nothrow guarantee when you can, but for most functions, the choice is between the basic and strong guarantees.
In the case of changeBackground, almost offering the strong guarantee is not difficult.
class PrettyMenu { // ... std::tr1::shared_ptr<Image> bgImage; // ... }; void PrettyMenu::changeBackground(std::istream& imgSrc) { Lock ml(&mutex); bgImage.reset(new Image(imgSrc)); // replace bgImage's internal pointer with the result of the "new Image" expression ++imageChanges; }
As I said, those two changes almost suffice to allow changeBackground to offer the strong exception safety guarantee. What's the fly in the ointment? The parameter imgSrc.
There is a general design strategy that typically leads to the strong guarantee, and it’s important to be familiar with it. The strategy is known as "copy and swap." In principle, it’s very simple. Make a copy of the object you want to modify, then make all needed changes to the copy. If any of the modifying operations throws an exception, the original object remains unchanged. After all the changes have been successfully completed, swap the modified object with the original in a nonthrowing operation (see Item 25).
This is usually implemented by putting all the per-object data from the "real" object into a separate implementation object, then giving the real object a pointer to its implementation object. This is often known as the "pimpl idiom," and Item 31 describes it in some detail. For PrettyMenu, it would typically look something like this:
struct PMImpl { // PMImpl = "PrettyMenu Impl." see below for why it's a struct std::tr1::shared_ptr<Image> bgImage; int imageChanges; }; class PrettyMenu { // ... private: Mutex mutex; std::tr1::shared_ptr<PMImpl> pImpl; }; void PrettyMenu::changeBackground(std::istream& imgSrc) { using std::swap; // see Item 25 Lock ml(&mutex); // acquire the mutex std::tr1::shared_ptr<PMImpl> // copy obj. data pNew(new PMImpl(*pImpl)); pNew->bgImage.reset(new Image(imgSrc)); // modify the copy ++pNew->imageChanges; swap(pImpl, pNew); // swap the new data into place }
The copy-and-swap strategy is an excellent way to make all-or-nothing changes to an object’s state, but, in general, it doesn’t guarantee that the overall function is strongly exception-safe.
To see why, consider an abstraction of changeBackground, someFunc, that uses copy-andswap, but that includes calls to two other functions, f1 and f2:
void someFunc() { // ... // make copy of local state f1(); f2(); // ... // swap modified state into place }
The problem is side effects. As long as functions operate only on local state (e.g., someFunc affects only the state of the object on which it's invoked), it’s relatively easy to offer the strong guarantee. When functions have side effects on non-local data, it's much harder. If a side effect of calling f1, for example, is that a database is modified, it will be hard to make someFunc strongly exception-safe. There is, in general, no way to undo a database modification that has already been committed; other database clients may have already seen the new state of the database.
There's no reason to perpetuate this state of affairs. When writing new code or modifying existing code, think carefully about how to make it exception-safe. Begin by using objects to manage resources. (Again, see Item 13.) That will prevent resource leaks. Follow that by determining which of the three exception safety guarantees is the strongest you can practically offer for each function you write, settling for no guarantee only if calls to legacy code leave you no choice. Document your decisions, both for clients of your functions and for future maintainers. A function's exception-safety guarantee is a visible part of its interface, so you should choose it as deliberately as you choose all other aspects of a function's interface.
Things to Remember:
- Exception-safe functions leak no resources and allow no data structures to become corrupted, even when exceptions are thrown. Such functions offer the basic, strong, or nothrow guarantees.
- The strong guarantee can often be implemented via copy-and-swap, but the strong guarantee is not practical for all functions.
- A function can usually offer a guarantee no stronger than the weakest guarantee of the functions it calls.
原文地址:https://www.cnblogs.com/zoneofmine/p/15250537.html
- 2017 Multi-University Training Contest - Team 9 1005&&HDU 6165 FFF at Valentine【强联通缩点+拓扑排序】
- 2017 Multi-University Training Contest - Team 9 1004&&HDU 6164 Dying Light【数学+模拟】
- Python3选择排序
- 【DeepMind 公开课-深度强化学习教程代码实战01】迭代法评估4*4方格世界下的随机策略
- Codeforces Round #434 (Div. 2, based on Technocup 2018 Elimination Round 1)&&Codeforces 861C Did yo
- Codeforces Round #434 (Div. 2, based on Technocup 2018 Elimination Round 1)&&Codeforces 861B Which
- 信用卡安全问题:被用户忽视的识别码
- Python3快速排序
- Python3插入排序
- Python3冒泡排序
- Python Selenium设计模式-POM
- 【Python学习笔记之一】Python关键字及其总结
- 前后端分离了,然后呢?
- 【Python学习笔记之二】浅谈Python的yield用法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Spring Security 基础入门
- Spring Security 安全认证
- Spring Security 自定义登录页
- CAS 服务端部署
- Spring Security 权限控制
- vue动态组件的用法
- elementUI中checekBox实现全选和反选以及实现在input中输入空格出现label分割
- centos 安装git
- centos 挂载硬盘
- centos 安装postgrep
- springboot集成PowerJob-openAPI和回调完整流程
- 使用 Redisson 实现的 redis 分布式锁在 SpringBoot 中的简单使用
- SpringBoot 的过滤器 Filter 配置
- CentOS 7 Redis 5.0.8 哨兵模式配置
- SpringBoot 项目的 Maven 多环境打包配置