我多年来没有在愤怒中使用它后,我正在刷新我的C ++知识。在编写一些代码来实现一些实践的数据结构时,我想确保我的代码是异常安全的。所以我试图在我认为合适的方式中使用std::auto_ptr
。简化,我就是这样:
class Tree
{
public:
~Tree() { /* delete all Node*s in the tree */ }
void insert(const string& to_insert);
...
private:
struct Node {
...
vector<Node*> m_children;
};
Node* m_root;
};
template<T>
void push_back(vector<T*>& v, auto_ptr<T> x)
{
v.push_back(x.get());
x.release();
}
void Tree::insert(const string& to_insert)
{
Node* n = ...; // find where to insert the new node
...
push_back(n->m_children, auto_ptr<Node>(new Node(to_insert));
...
}
所以我正在包装将指针放入容器的函数vector::push_back
,并依赖于按值auto_ptr
参数
如果Node*
调整大小失败,请确保删除vector
。
这是auto_ptr
的习惯用法,以节省我的一些样板
Tree::insert
?您可以建议的任何改进?否则我必须拥有
类似的东西:
Node* n = ...; // find where to insert the new node
auto_ptr<Node> new_node(new Node(to_insert));
n->m_children.push_back(new_node.get());
new_node.release();
如果我不是,那会使一行代码变得混乱 担心异常安全和内存泄漏。
(实际上我想知道我是否可以发布我的整个代码示例(大约300行)并要求人们批评它一般的惯用C ++用法,但我不确定这种问题是否适用于stackoverflow。 )
答案 0 :(得分:2)
编写自己的容器并不是惯用的:它非常特殊,并且大部分仅用于学习如何编写容器。无论如何,将std::autp_ptr
与标准容器一起使用肯定不是惯用的。事实上,这是错误的,因为std::auto_ptr
的副本不等同:在任何给定时间只有一个auto_ptr
拥有一个指针。
至于std::auto_ptr
的习惯使用,您应该始终在构建时命名auto_ptr
:
int wtv() { /* ... */ }
void trp(std::auto_ptr<int> p, int i) { /* ... */ }
void safe() {
std::auto_ptr<int> p(new int(12));
trp(p, wtv());
}
void danger() {
trp(std::auto_ptr<int>(new int(12)), wtv());
}
因为C ++标准允许参数以任意顺序进行求值,所以对danger()
的调用是不安全的。在trp()
中对danger()
的调用中,编译器可以分配整数,然后创建auto_ptr
,最后调用wtv()
。或者,编译器可以分配一个新的整数,调用wtv()
,最后创建auto_ptr
。如果wtv()
抛出异常,则danger()
可能会泄漏,也可能不泄漏。
但是,在safe()
的情况下,因为auto_ptr
是先验构造的,RAII保证无论wtv()
是否抛出异常都能正确清理。
答案 1 :(得分:2)
是的。
例如,请参阅Boost.Pointer Container库的界面。各种指针容器都具有一个插入函数,它带有一个auto_ptr
,它在语义上保证它们拥有所有权(它们也有原始指针版本,但是嘿:p)。
然而,有其他方法可以实现您在异常安全方面所做的工作,因为它只是内部的。要理解它,你需要了解可能出现的问题(即抛出)然后重新排序你的指令,以便在副作用发生之前完成可能抛出的操作。
例如,从你的帖子中取出:
auto_ptr<Node> new_node(new Node(to_insert)); // 1
n->m_children.push_back(new_node.get()); // 2
new_node.release(); // 3
让我们检查每一行,
new
将为您执行清理push_back
的调用可能会抛出std::bad_alloc
。这是唯一可能的错误,因为复制指针是一个无投掷操作如果你仔细观察,你会说你不必担心,如果你能以某种方式在2之前执行2,那实际上是可能的:
n->m_children.reserve(n->m_children.size() + 1);
n->m_children.push_back(new Node(to_insert));
对reserve
的调用可能会抛出(bad_alloc
),但如果它正常完成,则可以保证在size
等于capacity
之前不会重新分配,你尝试另一次插入。
如果构造函数抛出,new
的调用可能会丢失,在这种情况下new
将执行清理,如果完成,则会留下一个立即插入{{1}的指针由于上面的一行,保证不会抛出。
因此,vector
的使用可以在这里替换。尽管如此,尽管如此,您应该避免在功能评估中执行RAII初始化。
答案 2 :(得分:0)
我喜欢声明指针所有权的想法。这是c ++ 0x,std::unique_ptr
中的一个重要特性。但是std::auto_ptr
很难理解,致命甚至有点误导,我建议完全避免它。
答案 3 :(得分:-1)