【Exceptional C++(8)】代码的复杂性(二)
2018-01-29 本文已影响6人
downdemo
问题
- 如下函数是异常安全(出现异常时仍能正常工作)还是异常中立(能将所有异常传给调用者)的,假设所有被调用的函数都是异常安全的(可能抛出异常但没有任何副作用)?如果是异常安全,它支持basic guarantee还是strong guarantee?
String EvaluateSalaryAndReturnName(Employee e)
{
if (e.Title() == "CEO" || e.Salary() > 100000)
{
cout << e.First() << " " << e.Last() << " is overpaid" << endl;
}
return e.First() + " " + e.Last();
}
解答
- 该函数满足basic guarantee:出现异常时函数不产生资源泄漏
- 该函数不满足strong guarantee:如果函数由异常造成失败,程序的状态必须保持不变。这里有两个不同的副作用
- 一个"...overpaid..."消息被送到cout
- 一个名称字符串被返回
- 如果考虑到第二点函数就可以满足strong guarantee,因为异常产生时值不会返回
- 如果考虑到第一点函数仍不是异常安全的,原因是
- 如果消息的第一部分被送到cout,整个消息被完全送到cout前有异常抛出,此时已有一部分消息被输出了
- 消息成功输出,之后函数产生异常,这个消息也已经被送到cout了,尽管该函数因为异常失败
- 要满足strong guarantee,函数必须保证原子性,要么两件事都做要么都不做,要达到此要求可进行如下修改
String EvaluateSalaryAndReturnName(Employee e)
{
String result = e.First() + " " + e.Last();
if (e.Title() == "CEO" || e.Salary() > 100000)
{
String message = e.First() + " " + e.Last() + " is overpaid\n";
cout << message;
}
return result;
}
- 这段代码还不错,为了让整个string只使用一个<<调用,用换行符代替了endl,但仍有瑕疵
String theName;
theName = EvaluateSalaryAndReturnName(bob);
- 函数传值返回,String的拷贝构造函数被调用,然后调用拷贝赋值运算符拷贝到theName,如果这两个拷贝操作有任何一个失败函数副作用都会产生影响,消息已被完全输出,返回值也已完全构造,结果却丢失了。我们会想到避免拷贝操作来避免此问题,让函数接受一个non-const引用,并用于保存返回值
void EvaluateSalaryAndReturnName(Employee e, String& r)
{
String result = e.First() + " " + e.Last();
if (e.Title() == "CEO" || e.Salary() > 100000)
{
String message = e.First() + " " + e.Last() + " is overpaid\n";
cout << message;
}
r = result;
}
- 然而对r赋值仍然可能失败,这样一个副作用被完成,另一个没被完成,所以这种做法也不行。解决方法是使用动态内存分配,隐藏产生第二个副作用(返回值)的操作,同时保证第一个副作用(打印操作)被完成后,只使用不抛出异常的操作把结果安全返回给调用者,这种强安全性的代价是效率
shared_ptr<String> EvaluateSalaryAndReturnName(Employee e)
{
shared_ptr<String> result = new String(e.First() + " " + e.Last());
if (e.Title() == "CEO" || e.Salary() > 100000)
{
String message = e.First() + " " + e.Last() + " is overpaid\n";
cout << message;
}
return result;
}
- 提供强异常安全通常要放弃一部分性能
- 如果一个函数含有多重副作用,那么其总是无法成为强异常安全的,此时唯一的办法就是将函数分成几个函数,以使得每个分函数的副作用能被自动完成
- 并非所有函数都需要强异常安全性,原始代码和第一次修改的代码已经能满足basic guarantee了。许多情况下,第一次尝试的代码已经足够好用,能将副作用在异常情况下发生的可能性减到最小,而不必非得损失一定性能