我正在尝试编写一个基于Alexandrescu概念但使用c ++ 11习语的简单ScopeGuard.
namespace RAII
{
template< typename Lambda >
class ScopeGuard
{
mutable bool committed;
Lambda rollbackLambda;
public:
ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}
template< typename AdquireLambda >
ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)
{
_al();
}
~ScopeGuard()
{
if (!committed)
rollbackLambda();
}
inline void commit() const { committed = true; }
};
template< typename aLambda , typename rLambda>
const ScopeGuard< rLambda >& makeScopeGuard( const aLambda& …Run Code Online (Sandbox Code Playgroud) 在Herb Sutter的书Exceptional C++(1999)中,他在第10项的解决方案中有词:
"异常不安全"和"糟糕的设计"齐头并进.如果一段代码不是异常安全的,那通常是可以的并且可以简单地修复.但是,如果一段代码由于其底层设计而不能被设置为异常安全,那几乎总是这是其设计不佳的信号.
示例1:具有两种不同职责的职能很难使例外安全.
示例2:以必须检查自我分配的方式编写的复制赋值运算符可能也不是强烈异常安全的
"检查自我分配"一词是什么意思?
[查询]
Dave和AndreyT向我们展示了"检查自我分配"的含义.非常好.但问题还没有结束.为什么"检查自我分配"会伤害"异常安全"(根据Hurb Sutter的说法)?如果调用者尝试进行自我分配,那么"检查"就像没有发生任何分配一样.真的疼吗?
[备忘1]在Herb的书后面的第38项对象身份中,他解释了自我分配.
在编写一些针对C++ 17的代码时,我遇到了一个障碍,它决定了合并两个兼容的std :: unordered_maps的操作的异常安全性.根据目前的工作草案 §26.2.7,表91部分内容涉及以下条件a.merge( a2 ):
要求:
a.get_allocator() == a2.get_allocator().尝试提取每个元素
a2并a使用哈希函数和密钥相等谓词将其插入a.在具有唯一键的容器中,如果键中的元素a与元素的键相等a2,则不会从中提取该元素a2.后置条件:指针和对转移元素的
a2引用引用那些相同的元素但作为成员a.引用传递元素的迭代器和引用的所有迭代器a都将失效,但剩余元素的迭代器a2将保持有效.抛出:除非散列函数或键等式谓词抛出,否则无效.
值得注意的是,这些条件强烈地让人联想到普通关联容器(std :: map)的要求,如§26.2.6,表90所示a.merge( a2 ):
要求:
a.get_allocator() == a2.get_allocator().尝试提取每个元素
a2并a使用比较对象将其插入a.在具有唯一键的容器中,如果键中的元素a与元素的键相等a2,则不会从中提取该元素a2.后置条件:指针和对转移元素的
a2引用引用那些相同的元素但作为成员a.引用传递元素的迭代器将继续引用它们的元素,但它们现在表现为迭代器a,而不是a2.抛出:除非比较对象抛出,否则无效.
我需要合并两个std :: unordered_maps和相同数量的元素,我可以确保这两个元素在两个容器中都是唯一的,这意味着包含合并结果的地图将使之前拥有的元素数量增加一倍,并且容器合并 …
在cppref中,以下内容一直持续到C++ 17:
f(std::shared_ptr<int>(new int(42)), g())如果g在之后调用new int(42)并抛出异常,则可能导致内存泄漏的代码,同时f(std::make_shared<int>(42), g())是安全的,因为两个函数调用永远不会交错.
我想知道在C++ 17中引入了哪些更改使其不再适用.
考虑以下:
std::vector<std::unique_ptr<int>> ptrsToInts;
ptrsToInts.emplace_back(new int);
Run Code Online (Sandbox Code Playgroud)
如果在向量中发生重新分配,并且失败(抛出std::bad_alloc),我是"安全"还是会泄漏int?
C++ 11 23.3.6.5 [vector.modifiers]/1说:
如果除了复制构造函数之外抛出异常,则移动构造函数,赋值运算符或移动赋值运算符
T或通过任何InputIterator运算都没有效果.
这似乎表明这是一个潜在的问题.也就是说,如果存在"无效果",则unique_ptr从未构造过,因此delete不会发生依赖于该指针的析构函数行为.(这可能表明emplace_back应该禁止容器的unique_ptrs)
我的理解是C++ 隐式生成的赋值运算符执行成员方式的复制(这似乎也得到了这个答案的证实).但是,如果在成员副本期间抛出异常(例如,因为无法分配该成员的资源),被复制的对象是否会陷入无效状态?
换句话说,隐式生成的赋值运算符是仅实现基本保证,而不是强保证?
如果我们想要为我们的类副本提供强有力的保证,我们是否必须使用copy-and-swap惯用法手动实现赋值运算符?
只是为了澄清一下,make_unique当你在表达式中有多个分配而不只是一个时,使用只会增加异常安全性,对吗?例如
void f(T*);
f(new T);
Run Code Online (Sandbox Code Playgroud)
是完全异常安全(就分配和东西而言),而
void f(T*, T*);
f(new T, new T);
Run Code Online (Sandbox Code Playgroud)
是不正确的?
在成员函数的范围内,我想暂时将成员变量设置为某个值.
然后,当此函数返回时,我想将此成员变量重置为给定的已知值.
为了防止异常和多次返回,我已经使用类似简单的RAII来完成它.它是在成员函数的范围内定义的.
void MyClass::MyMemberFunction() {
struct SetBackToFalse {
SetBackToFalse(bool* p): m_p(p) {}
~SetBackToFalse() {*m_p=false;}
private:
bool* m_p;
};
m_theVariableToChange = true;
SetBackToFalse resetFalse( &m_theVariableToChange ); // Will reset the variable to false.
// Function body that may throw.
}
Run Code Online (Sandbox Code Playgroud)
看起来很平常,我想知道是否有任何这样的模板类在C++标准库中这样做?
我有一个函数可以作为接收器参数传入大量数据.我的BigData类型已经是C++ 11感知的,并且具有全功能的移动构造函数和移动赋值实现,所以我可以逃脱而不必复制该死的东西:
Result processBigData(BigData);
[...]
BigData b = retrieveData();
Result r = processBigData(std::move(b));
Run Code Online (Sandbox Code Playgroud)
一切都很好.但是,我的处理函数可能偶尔会在运行时失败,从而导致异常.这不是一个真正的问题,因为我可以修复东西并重试:
BigData b = retrieveData();
Result r;
try {
r = processBigData(std::move(b));
} catch(std::runtime_error&) {
r = fixEnvironmnentAndTryAgain(b);
// wait, something isn't right here...
}
Run Code Online (Sandbox Code Playgroud)
当然,这不起作用.
由于我将数据移动到处理函数中,所以当我到达异常处理程序时,b将不再可用.
这可能会大大降低我对以次值传递次要参数的热情.
所以问题是:如何在现代C++代码中处理这样的情况?如何检索以前移入无法执行的函数的数据访问?
您可以更改实施和接口都BigData和processBigData你请.然而,最终的解决方案应该尽量减少原始代码在效率和可用性方面的缺点.
c++ ×10
exception-safety ×10
c++11 ×3
c++17 ×2
exception ×2
c++-faq ×1
emplace ×1
gcc7 ×1
idioms ×1
lambda ×1
make-shared ×1
raii ×1
return ×1
scopeguard ×1
unique-ptr ×1