这是一个很好的std :: auto_ptr<>用例?

时间:2009-11-22 03:33:19

标签: c++ auto-ptr exception-safe

请假设我有一个接受指针作为参数的函数。此函数可以抛出异常,因为它使用std::vector<>::push_back()来管理此指针的生命周期。如果我这样声明:

void manage(T *ptr);

并将其称为:

manage(new T());

如果它抛出异常将指针推入std::vector<>,我实际上有内存泄漏,不是吗?

声明这样的功能:

void manage(std::auto_ptr<T> ptr);

解决我的问题?

我希望它首先在堆栈上分配std::auto_ptr(我猜不会抛出异常)并让它获得指针的所有权。安全

然后,在函数内部,我会将原始指针推入std::vector<>,这也是安全的:如果失败,指针将不会被添加,但智能指针仍将拥有指针所以它会被摧毁。如果推送成功,我将删除智能指针对该指针的所有权并返回:这不会抛出异常,因此它总是没问题。

我的理论是否正确?

- 编辑 -

不,我想我做不到。这样做需要对rvalue采用非const引用(从智能指针中取走所有权)。我必须写

std::auto_ptr<T> ptr(new T());
manage(ptr);

为了工作,在我的情况下这是非常不方便的。我正在写这个,这样我就可以实现RAII而不会污染很多代码。那样做那件事无济于事。那将是22号。

- 编辑2 -

拉出Jason Orendorff所说的内容以供读者快速参考,最终的解决方案似乎如下:

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

这解决了对rvalue无用的非const引用的问题。

当我完成本课程时,我正在编写代码,我会将其发布回来,以防有人发现它有用。

- 编辑3 -

好的,这里有很多讨论,我之前应该澄清一些关键点。一般来说,当我在stackoverflow发布时,我试着解释我的问题背后的原因,一般来说,这是完全没用的。所以这次我认为我应该直截了当。结果证明它不能很好地运行XD

不幸的是,我的大脑现在陷入僵局,所以我想我甚至无法正确解释我最初想要实现目标的内容。我试图为原子操作和异常安全的代码编写找到一个很好的解决方案,适合很多情况,但实际上,我无法处理它XD我认为这是我只会随着时间掌握的那种东西。

我是一位非常新的C ++程序员,我的重点是游戏开发。当在游戏引擎中抛出异常时,它就是执行的结束。系统将为我的进程释放所有内存,因此如果一个或两个指针在这里和那里泄漏并不重要。现在我正在开发一个服务器应用程序,我发现很难处理异常,因为异常不能使服务器崩溃;它必须“使请求崩溃”。

也就是说,“嗯,客户端,不幸的是开发人员没有预见到这种情况,所以你必须稍后再尝试(到目前为止,这与游戏引擎基本相同,没有任何东西我只是把它分离到了一个请求的上下文中,而不是整个过程。但是不要惊慌,因为一切都处于有效状态(这是其中一个区别,但是。进程没有终止,因此操作系统无法为您释放资源;此外,您必须注意撤消到目前为止的操作,这样您就不会完全锁定用户的帐户,例如,甚至是服务器提供的完整服务。“。

我会编写越来越多的代码并记下我的问题,以便下次我可以写一个更好的问题。我现在不准备问这个,我很抱歉。

非常感谢您的回复,我非常喜欢stackoverflow。令我惊讶的是,我的问题得到了多快的回答以及你的答案有多启发。感谢。

4 个答案:

答案 0 :(得分:4)

你可以这样做,但是如果没有抛出异常,你仍然需要清理,这看起来有点麻烦。

如果您使用boost::shared_ptr之类的东西(我相信这样的东西也在TR1库中 - 作为一个示例,请参阅MS's implementation),您可能会忘记在事情发生时必须进行清理计划。

要完成这项工作,你需要让你的向量接受boost::shared_ptr < T >实例,然后你只需要清理你的原始实例,并且在一切顺利的情况下它会在向量中保留实例。在出现问题的情况下,所有boost::shared_ptr实例都将被销毁,您最终仍然没有泄漏。

使用智能指针,它是关于选择适合任务的一个,在这种情况下,共享所有权(或简单的所有权转移)似乎是一个目标,因此在大多数平台上有比std :: auto_ptr更好的候选者。

答案 1 :(得分:4)

通常,使用std::auto_ptr作为一种函数参数,明确地告诉调用者函数将获取对象的所有权,并将负责删除它。如果您的函数符合该描述,则无论出于何种其他原因,请务必使用auto_ptr

答案 2 :(得分:1)

是的,没关系。 (见下面的编辑。)(jkp可能会看到我错过的东西,但我不认为你“仍然需要在抛出异常的情况下清理“因为,正如你所说,在这种情况下,auto_ptr会为你删除对象。”

但我认为将调用者隐藏auto_ptr shenanigans会更好:

void manage(T *t) {
    std::auto_ptr<T> p(t);  // to delete t in case push_back throws
    vec.push_back(t);
    p.release();
}

编辑:我最初写的是“是的,没关系”,指的是最初的manage(auto_ptr<T>)计划,但我尝试了它,发现它不起作用。构造函数auto_ptr<T>::auto_ptr(T *)explicit。编译器不允许您编写manage(new T),因为它无法将该指针隐式转换为auto_ptr。无论如何,manage(T *)是一个更友好的界面!

答案 3 :(得分:1)

我认为已经产生了足够的讨论以保证另一个答案。

首先,回答实际问题,是的,在所有权转移发生时,通过智能指针传递参数是绝对合适的(甚至是必要的!)。通过智能指针传递是实现这一目标的常用习惯。

void manage(std::auto_ptr<T> t) {
    ...
}
...

// The reader of this code clearly sees ownership transfer.
std::auto_ptr<T> t(new T);
manage(t);

现在,有一个很好的理由为什么所有智能指针都有显式构造函数。考虑以下功能(如果它引起您的喜爱,请在心理上将std::auto_ptr替换为boost::shared_ptr):

void func(std::auto_ptr<Widget> w, const Gizmo& g) {
    ...
}

如果std::auto_ptr有一个隐式构造函数,那么这个代码会突然编译:

func(new Widget(), gizmo);

它出了什么问题?几乎直接来自“Effective C ++”,第17项:

func(new Widget(), a_function_that_throws());

因为在C ++中,参数评估的顺序是未定义的,所以您可以按以下顺序评估参数:new Widget()a_function_that_throws()std::auto_ptr构造函数。如果函数抛出,则表示存在泄漏。

因此,在传递给函数之前,所有将要发布的资源需要在RAII类中构建时被包装。这意味着必须在将它们作为参数传递给函数之前构造所有智能指针。使用const引用使智能指针可复制构造或隐式构造可能会鼓励不安全的代码。明确的构造可以实现更安全的代码。

现在,你为什么不做这样的事情?

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

正如已经提到的,接口习语告诉我,我可以传递一个我拥有的指针,然后我将其删除。所以,没有什么能阻止我这样做:

T item;
manage(&t);

// or
manage(&t_class_member);

当然,这是灾难性的。但你会说“当然我知道界面意味着什么,我永远不会那样用”。但是,您可能希望稍后向函数添加额外的参数。或者某人(不是你,或者你3年后)出现在这段代码中,他们可能不会像你那样看待它。

  1. 这个假设的“其他人”可能只看到没有评论的标题,并且(正确地)假设缺乏所有权转移。
  2. 他们可能会看到如何在其他代码中使用此函数,并在不查看标题的情况下复制用法。
  3. 他们可能会使用代码自动完成来调用函数而不读取注释或函数,并假定缺少所有权转移。
  4. 他们可能会编写一个包含manage函数的函数,但其​​他人将使用包装函数,并且会遗漏原始函数的文档。
  5. 他们可能会尝试“扩展”您的代码,以便所有旧代码编译(并自动变得不安全):

    void manage(T *t, const std::string tag = some_function_that_throws());
    
  6. 正如您所看到的,显式构造智能指针使得在上述情况下编写不安全代码变得更加困难。

    因此,我不建议反对数十年的C ++专业知识来制作可感知的“更好”和“有趣”的API。

    我的2c。