如何有效地处理需要所有权的临时助手?

时间:2017-01-30 10:44:34

标签: c++ memory-management c++17

C ++内存管理的一个方面目前困扰着我。 C ++中用于内存管理的主要概念是所有权。每个对象都由一个或(通过共享指针)拥有多个东西。那些东西可以是其他对象或范围/块。可以转移所有权(通过唯一指针)。一旦事物(对象,范围/块)不再存在,它就会删除它拥有的所有东西的所有权,并且所有没有其他所有者的对象也不再存在。

关于API,如果一个人取得对象的所有权,则必须表达:

  • 取得独家所有权是通过按价值或右值参考取得某些东西来表达的。
  • 使用共享指针表示共享所有权。
  • 使用左值参考表示不取得所有权。

在大多数情况下这很好用,但我遇到一种情况有困难:我有一个不需要取得所有权的功能,但它使用临时辅助对象仅引用一些其他对象,即它们具有指针或引用的成员。如果我想避免悬空引用,这基本上意味着帮助程序对象需要声明对它们引用的对象的共享所有权。现在,我有两个选择:

  1. 修改函数以获取共享所有权指针,但是,它正在泄漏实现细节。基本上,所有函数的所有参数都是这样的...就像它们在大多数垃圾收集或引用计数语言中一样。
  2. 在我的帮助程序类中使用引用/原始指针来表示共享所有权,并依赖用户确保在共享资源之前销毁帮助程序类实例。
  3. 我目前没有明确的指导方针来决定何时做出决定,大多数情况下我倾向于使用后一种方法。但是现在,我介绍了一个不容易找到的错误,花了我一些时间。

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

    这似乎是一种合理的方法,但在走这条路之前我想找到一些东西:

    1. 这真的是一种合理的方法,还是我为我的软件设计API的方式有问题?
    2. 标准库中有什么我不知道的东西让这个任务变得容易吗?
    3. 或任何第三方图书馆?
    4. 添加代码示例以澄清:

      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 );
      }
      

      AutoAddEndLineAdaptor课程要求共享所有权变得安全,即像breakStuff中那样滥用,但printStuff没有。

3 个答案:

答案 0 :(得分:2)

  

我有一个不需要取得所有权的功能,但它使用需要共享所有权的临时助手对象。

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

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

如果该函数不能取得所有权,但必须使用拥有所有权的帮助程序来实现,那么您就陷入了僵局。要么调整要求,要么放弃设计。

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

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

  

AutoAddEndLineAdaptor类需要共享所有权,但printStuff不需要。

既不拥有引用的对象。他们都引用了这个对象。在AutoAddEndLineAdaptor中使用printStuff是安全的。

返回引用局部变量的AutoAddEndLineAdaptor是不行的,例如在breakStuff中。

答案 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);
}

当所有共享指针都消失后,删除器被调用...但由于它什么都不做,它并没有真正影响到真正的&#39; ptr的所有权。

当然,你真的,真的确保它做到了你所期望的。这种事情在两个方面非常容易出错:

  • 您必须确保function_that_does_not_own确实没有调用任何会导致ptr的真正所有者释放该对象的内容。 (当然,对于任何使用指针而不共享其所有权的函数,你必须这样做)
  • 您必须确保function_that_does_not_own确实没有调用任何会产生副作用的内容,这些副作用最终会使ptr的生命周期超出此函数调用范围。

很容易错过会违反其中一个或两个要点的边缘条件 - 实际上可能非常难以确保function_that_does_not_own不需要参与在指针所有权。

如果有任何问题(并且可能有你的函数必须使用其他想要共享指针所有权的对象),那么你应该假设你的函数确实需要共享指针的所有权,并且拿一个shared_ptr参数。

答案 2 :(得分:1)

  

添加代码示例以澄清:

您的示例显示了问题的基础。

printStuff中,类型AutoAddEndLineAdaptor正是您所描述的:临时助手对象。这是一个实现细节。它不会在函数结束后持续存在。

breakStuff中,情况并非如此。 AutoAddEndLineAdaptor不再仅仅是函数的实现细节;它是您系统界面的部分。哦,当然,你(ab)使用auto隐藏了这个名字。但是你无法隐瞒AutoAddEndLineAdaptor现在是你系统界面的一部分这一事实。因此,breakStuffAutoAddEndLineAdaptor和用户提供的对象之间的关系也是该界面的一部分。

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

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

并非流的每个operator<</>>重载都能有效地实现此目的。因此,这不是一种不常见的策略。您的问题是用户并未敏锐地意识到这一点,因为您间接地将其返回,隐藏在auto后面。

或者您可以breakStuff声明对参数的所有权,以便将其转移到返回值。如果这在逻辑上是breakStuff的含义,那么它是有道理的。

就个人而言,我会根据您的具体情况选择选项1。但这是一种逐案的事情。

顺便说一句,这也是为什么你应该避免 auto返回扣除,除非它实际上是必要的(即:返回类型是一个复杂的类型,并且它对用户来说是显而易见的)。如果您已将AutoAddEndLineAdaptor放入返回类型中,那么您所做的事情会更明显。