【问题标题】:RAII class designRAII类设计
【发布时间】:2012-04-17 11:08:42
【问题描述】:

假设我有一个以 RAII 方式管理某些资源的类:

class C
{
   HANDLE hResource_;

   // prevent sharing the ownership over the resource among multiple instances of C
   C(const C&);
   C& operator=(const C&);

public:
   C() : hResource_(INVALID_HANDLE){}

   C(int arg1, const std::string& arg2,...)
   {
      ...
      allocResource(arg1, arg2, ...);
      ...
   }

   ~C
   {
      ...
      FreeResource(hResource_);
      hResource_ = INVALID_HANDLE;
      ...
   }

   void allocResource(int arg1, const std::string& arg2, ...)
   {
      if(hResource_ == INVALID_HANDLE)
      {
          hResource_ = AllocateResource(arg1, arg2,...);
      }
   }

   HANDLE handle() {return hResource_;}
};

它的构造函数需要一些资源分配所需的参数,我可以创建它的一个实例,使用它并让它存在于某个范围内:

// some global function 
void goo()
{
   C c(123, "test");
   UseResource(c.handle(),...);
   ... 
}

假设我现在希望C 的一个实例成为某个类的成员,并且想要延迟在C 的c-tor 中发生的资源分配。这需要C 的默认c-tor 和一些C 执行资源分配的成员函数(例如allocResource() 调用AllocateResource())。

class A
{
   C c_;

public:
   void foo1()
   {
      ...
      c_.allocResource(123, "test"); 
      UseResource(c_.handle(),...);
      ...
   }   

   void foo2()
   {
      ...         
      UseResource(c_.handle(),...);
      ...
   }   
};

通过使用专用函数,我们以某种我不喜欢的方式暴露了C 的内部结构。

我的问题是:这种方法是启用延迟初始化的常用方法吗?有其他选择吗?


编辑:这是关于以下(MSalters')建议的可能类设计:

class C
{
   HANDLE hResource_;

   // prevent sharing the ownership over the resource 
   // among multiple instances of C
   C(const C&);
   C& operator=(const C&);

public:      

   // prevent object creation if resource cannot be acquired
   C(int arg1, const std::string& arg2,...)
   {          
      hResource_ = AllocateResource(arg1, arg2,...);

      // assumption: AllocateResource() returns 
      // INVALID_HANDLE in case of failure
      if(hResource_ == INVALID_HANDLE)
         throw resource_acquisition_exception();
   }

   ~C
   {
      ...
      FreeResource(hResource_);
      hResource_ = INVALID_HANDLE;
      ...
   }

   HANDLE handle() {return hResource_;}
};

class A
{
   std::unique_ptr<C> c_;

public:
   void foo1()
   {
      try
      {
         ...
         c_ = std::unique_ptr<C>(new C(123, "test"));
         UseResource(c_->handle(),...);
         ...
      }
      catch(const resource_acquisition_exception& exc)
      {
         ...
      }
      catch(...)
      {
         ...
      }
   }   

   void foo2()
   {
      ...         
      UseResource(c_->handle(),...);
      ...
   }   
};

【问题讨论】:

  • 实现一个移动构造函数怎么样?这样您就可以在 foo1 中创建一个新的 C 对象并将其分配给 c_。在 c++03 中,您可以通过使用带有 C 非 const 引用的构造函数来做到这一点。
  • 您应该将可能的解决方案转化为答案,而不是将其置于问题中。

标签: c++ raii


【解决方案1】:

不,这不是执行 RAII 的常用方法。事实上,它根本不是 RAII。如果您无法为C 分配必要的资源,请不要创建C

【讨论】:

  • 我现在可以看到 RAII 和延迟初始化不能齐头并进。但让我感到困惑的是,我们不能让C 成为A 的成员,而是让HANDLE 作为其成员。
  • 如果结构正确,它们确实是齐头并进的。在您的班级A 中,添加std::unique_ptr&lt;C&gt; c_。然后,C 类可以成为HANDLE 的 RAII 包装器。您现在有 C 的延迟初始化,而不仅仅是 HANDLE。顺便说一句,不要忘记 C 的复制 ctor 和分配。
  • @MSalters:实际上这也(或多或少)依赖于延迟初始化std::unique_ptr
  • @MSalters C 确实旨在成为HANDLE 的 RAII 包装器。因此,如果C 实例本身是A 的成员,那么基本上不可能进行延迟初始化?您建议的概念(unique_ptr)是解决此问题的常用方法吗?
  • @KillianDS:确实。但这是关注点分离的一个很好的例子。 class C 知道HANDLE 的详细信息。 std::unique_ptr&lt;C&gt; 间接知道句柄,因为它知道调用 C::~C,但 std::unique_ptr 的作者不必输入这些信息。这是 RAII 类的一个好处:它们与标准类配合得很好。跨度>
【解决方案2】:

问题确实是你暴露了 C 的内部,但你已经在使用 handle() 函数这样做,这已经限制了进行惰性实例化的机会。

如果真的调用 C 来做某事而不是仅仅获取处理程序会更容易。但是,由于 handle() 是一个 getter,并且您已经可以在构造函数中传递所需的参数(无需实例化,而是通过存储参数),您可以在 handle() 中检查 hResource_ 是否有效,如果不是,则分配资源(并在分配失败时抛出异常)。

【讨论】:

    猜你喜欢
    • 2018-08-07
    • 2012-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-20
    相关资源
    最近更新 更多