2017-01-30 90 views
1

C++中的内存管理的一个方面是目前困扰着我。 C++中用于内存管理的主要概念是所有权。每个对象都由一个或(通过共享指针)拥有多个事物。这些东西可以是其他对象或范围/块。所有权可以转让(通过独特的指针)。一旦事物(对象,范围/块)不复存在,它就会放弃它所拥有的所有权,并且所有没有其他所有者的对象也不再存在。如何有效处理需要所有权的临时帮手?

关于API的,一个有表达如果取得所有权对象或不:

  • 以独占所有权由通过值或右值引用采取什么表示。
  • 通过使用共享指针来表达共享所有权。
  • 未采用所有权通过使用左值引用来表示。

这工作得非常好在大多数情况下,但我有麻烦一种情况:我有一个并不需要采取所有权的功能,但它使用的是仅仅是作为参考临时器辅助对象一些其他的对象,即他们有成员是指针或引用。如果我想避免悬而未决的引用,这基本上意味着辅助对象需要对它们引用的对象声明共享所有权。现在,我有两种选择:

  1. 修改函数采取共享所有权指针,但然后,它泄漏实现细节。基本上,所有函数的所有参数都是这种类型的......就像他们在大多数垃圾收集或引用计数语言中一样。
  2. 在我的助手类中使用引用/原始指针来表示共享所有权,并依赖用户确保在共享资源之前销毁助手类实例。

对于何时做出哪一个决定,我目前没有明确的指导方针,大多数情况下我倾向于使用后一种方法。但是现在,我介绍了一个不容易找到它并花费我一些时间的错误。

我想了解如何在未来避免这些情况。在调试过程中,我编写了一些能够检测到这种情况的代码(通过使用可在生产模式下停用的引用计数)(但将它用于错误的对象;-))。

这似乎是一个合理的做法,但会在这条道路之前,我想找到一些东西出来:

  1. 这真的是一个合理的方法或者是有什么错我设计的API的方式为我的软件?
  2. 标准库中是否有任何我不知道的事情使得这个任务变得简单?
  3. 或任何第三方库?

添加的澄清代码示例:

struct AutoAddEndLineAdaptor 
{ 
    template <class T> 
    AutoAddEndLineAdaptor & 
    operator<<(T && t) 
    { 
     _out << std::forward<T>(t) << std::endl; 
     return * this; 
    } 

    std::ostream & _out; 
}; 

void printStuff(std::ostream & out) { 
    AutoAddEndLineAdaptor{ out } << "Hello" << "World"; 
} 

auto breakStuff(std::string file) { 
    std::ofstream o(file); 
    return AutoAddEndLineAdaptor(o); 
} 

的类需要共享所有权成为安全的,即在breakStuff难以使用,但printStuff不。

+1

你可以添加代码示例吗? –

+0

[标签:Rust]在其类型系统中有生命时间来完全处理这些情况。 – Jarod42

+0

“*如果我想避免悬挂引用,这基本上意味着辅助对象需要对它们引用的对象声明共享所有权。*”如果这是真的,如果它们确实是临时对象,那么它们不需要声称拥有这些物品的所有权他们只是参考它们。 –

回答

2

我有一个函数不需要拥有所有权,但它使用需要共享所有权的临时帮助程序对象。

如果函数必须使用需要(共享)所有权的帮助程序,则该函数需要(共享)所有权。解决方案:使用共享指针,并忘记该函数不需要它的假设。

如果不需要使用帮助器来实现该功能,并且该功能不能获得所有权,那么使用这些帮助器不是实现该功能的可接受的方式。解决方案:不要使用助手。而是使用不需要所有权的东西。

如果函数不能取得所有权,但必须使用拥有所有权的帮助器来实现,那么您处于僵局。要么调整要求,要么放弃设计。


请注意,可以假定函数内引用对象的所有权。只要临时所有权被释放并且不会超越职能,它就不构成所有权。

因此,只要您能证明实施细节在任何情况下都不会泄漏,您就可以在技术上暂时承担所有权。当涉及例外情况时,这可能会有问题。


的类需要共享所有权,但printStuff没有。

既不拥有引用的对象。它们都引用该对象。在printStuff内使用是安全的。

返回引用局部变量的(如breakStuff)并不正确。

+0

我很难接受所有权是传递性的。这就排除了lambdas中的引用捕获 - 适用于非可复制类型的适配器模式,据我所见,大量的基于范围的编程等等。它使用了很多可用的语言组合。 –

+0

@MarkusMayr​​为什么它会排除在lambdas中通过引用进行捕获?引用并未取得所有权。你也不会在基于范围的循环中隐含所有权。如果通过*“需要共享所有权的帮助对象”*,您实际上打算说帮助者需要引用对象,那么您没有问题。 – user2079303

+1

我认为共享所有权的目的是避免仅仅引用对象,因为后一种方法很容易导致引用不稳定。你能否说明共享所有权和引用对象之间的区别是什么?我认为这是我看似无法提问的答案的一部分。 –

1

您可以通过包装原始指针到有有删除这是一个无操作shared_ptr情况下,创造一种短暂的所有权制度:

void function_that_does_not_own(T *ptr) 
{ 
    shared_ptr<T> non_deleting_share(ptr, [](T*){}); 
    function_that_requires_shared(non_deleting_share); 
} 

当所有的共享指针都不见了的缺失者得被调用......但由于它什么都不做,它并不真正影响ptr的'真正'所有权。

当然,你真的,真的确保它做你期望的。这样的事情是非常容易出错有两种方式:

  • 你必须确保function_that_does_not_own确实不调用任何会导致ptr真正所有人释放的对象。(当然,对于任何使用指针但不共享其所有权的函数,您必须执行此操作)
  • 您必须确保function_that_does_not_own确实不会调用任何会产生最终期望ptr的副作用生命期延长到这个函数调用之外。

这是很容易错过边界条件,将违反一个或两个要点—它有时是很难真正保证function_that_does_not_own并不需要参加指针的所有权。

如果有任何问题(并且可能有您的函数需要使用其他对象来共享指针的所有权),那么您应该假设您的函数确实需要共享指针的所有权,并采取shared_ptr的说法。

1

添加代码示例澄清:

你的例子显示了你的问题的基础。

printStuff中,类型正如您所描述的那样:一个临时帮助对象。这是一个实现细节。它不会持续超过函数的结尾。

breakStuff中,情况并非如此。 不再仅仅是函数的实现细节;它是你系统的接口的部分。哦,当然,你已经(ab)使用auto来隐藏名字。但是你不能隐藏现在是你的系统界面的一部分。因此,breakStuff,与用户提供的对象之间的关系也是该接口的一部分。

您正在处理临时助手。问题是你没有认识到临时助手实际上不是临时助手的情况。

breakStuff间接返回到对象的用户拥有的参考。这是它的界面的基本部分。因此,您需要决定如何最好地通知用户这一点。 breakStuff可能对它有一个评论,通知用户它返回的对象包含对参数的引用,所以用户必须避免删除它,直到它们完成返回类型。

这并不是说所有的operator<</>>过载流有效地做到这一点。所以这不是一种不常见的策略。你的问题在于用户并没有清楚地意识到这一点,因为你间接地将它返回,隐藏在auto之后。

或者,您可以通过参数breakStuff主张所有权,以便它可以被转移到返回值。如果这在逻辑上是breakStuff是关于,那么它是有道理的。

就我个人而言,我会选择1,为您的具体情况。但这是个案例。

顺便说一句,这也是为什么你应该避免auto返回扣,除非它的实际需要(即:返回类型是一个复杂的类型,其用途是显而易见的用户)。如果您将置于返回类型中,那么您所做的更明显。

+0

我很抱歉使用'auto'。我似乎并没有对这个例子的这个方面进行很多的思考。如果你不介意,我会在上面的例子中改变它。关于我的问题,我认为我希望编译器在大多数类似'breakStuff'的情况下失败,并且我正在寻找这个问题的最优雅的解决方案,这为生产版本增加了几乎没有开销。 –