【问题标题】:C++ Style: copyable handle wrapper classesC++ 风格:可复制句柄包装类
【发布时间】:2013-06-24 06:59:29
【问题描述】:

让包装共享句柄的类可复制是否可以接受 C++ 风格?

我经常发现自己编写的类通过在类接口后面持有一个 shared_ptr 来隐藏一些粗糙的 C 库或操作系统资源的细节。例如

class window
{
public:
     window() : m_handle(OsCreateWindow(), OsDestroyWindow) {}

     void make_it_dance();
     void paint_it_red();
private:
     shared_ptr<OS_WINDOW_HANDLE> m_handle;
}

因为类是可复制的并且shared_ptr 做了艰苦的工作,所以实例可以随意传递,没有任何东西泄漏或被破坏两次。所以,从技术上讲,一切都很好。我已经这样做了很多年了。

但最近我开始怀疑这是不是真的很好的风格。毕竟,句柄末尾的对象并没有在类实例被复制时被复制。让所有这些类都不可复制,让用户非常清楚他们正在处理对同一对象的引用会更好吗?现代 C++ 非常重视“基于价值”,而在实例之间共享后端资源似乎违背了这一原则。

然而,结果是我的大部分代码都会处理指针,即使它们是智能指针。这似乎是倒退了一步。

【问题讨论】:

    标签: c++ coding-style shared-ptr raii idioms


    【解决方案1】:

    就个人而言,我不允许复制并同意使用共享指针是一种倒退。我还要补充一点,每个“Window”实例都应该包含一个唯一的 OS_WINDOW_HANDLE。

    您还可以使用 MFC 方法在对象之间传递句柄。 MFC 使用 Attach 和 Detach 方法,如下所示...

    class Window
    {
    public:
      void Attach (OS_WINDOW_HANDLE handle)
      {
        ASSERT(NULL == m_handle);  // or you could destroy the existing handle
        m_handle = handle;
      }
    
      OS_WINDOW_HANDLE Detach()
      {
        OS_WINDOW_HANDLE retVal = m_handle;
        m_handle = NULL;
        return retVal;
      }
    
    private:
      // disable copy constructor and assignment
      Window(const Window&);
      Window& operator=(const Window&);
    };
    

    【讨论】:

      【解决方案2】:

      如果句柄指向的资源不需要需要复制(浅拷贝),请使用std::shared_ptr。如果资源确实需要复制(深拷贝),您应该使用std::unique_ptr。如果不需要复制,请使用std::unique_ptr

      【讨论】:

      • 您是否建议将这些作为实现我所描述的不同行为的方法?我知道如何获得这些行为。问题在于哪种行为更好。
      【解决方案3】:

      我想我明白你的困境是什么。我有一个相当愚蠢的建议。

      由于您的问题不是功能性的,而是期望复制您的 window 实例创建的,如果您将这样的类命名为 window_handle 而不是 window 会不会更好?

      这意味着这只是某个窗口的句柄,复制它不会创建新窗口或类似的东西,它只是复制一个句柄。

      为了强调,我建议你保留你的设计(这对我来说似乎是一个很好的设计,而且似乎对你很有效),只是改变你的命名来改变对更高层代码的期望。

      【讨论】:

      • 这不是一个愚蠢的建议。但我对期望复制的实例相互独立的“基于值的”C++ 的含义很感兴趣。
      • 起初我虽然这个窗口句柄的想法只是平行shared_ptr,它具有值语义(对一个副本的操作不会明显影响其他副本的行为)但后来我意识到这并不t 适用于window_handle,因为它具有完整的窗口界面,并且非常量操作肯定会影响其他人的行为。解决方案是为可复制的window_handle 提供一个访问器方法,该方法返回一个不可复制的window,后者拥有接口的其余部分。但到那时我基本上已经有了shared_ptr typedef。
      • @thehouse:我相信人们的知识和经验比我能帮助你的要多。但从我的角度来看,句柄/智能指针/ID 类和对象并没有错。毕竟,shared_ptr 正是这样一个类(其复制的实例彼此独立),而您的类只是包装了一个shared_ptr
      • @thehouse:考虑迭代器的情况。它们与您的情况相似,因为它们是指向容器的轻量级对象。并且每个人都明白,您可以通过迭代器更改容器(更改元素、删除元素并重新分配整个事物等)。它被理解和接受。同样,shared_ptr 也是如此。您可以通过shared_ptr 更改底层对象,每个人都理解并接受这一点。因此,尽管您关于值语义的观点是有效的,但它们并不普遍;标准库本身包含一些例外。
      • 如果您阅读我的其他回复,您会发现我也有同样的想法,但事实并非如此。一个shared_ptr 和它的副本不能改变它的方法的可观察行为(好的,除了两个揭示底层引用计数,所以我猜它不是 100%)。共享指针可以改变其行为,但这是 shared_ptr 值的变化。 window_handleshared_ptr 不同,它直接在其接口中具有窗口方法。对一个副本的操作会改变另一个副本的行为。
      【解决方案4】:

      一个可能的答案(以及提示问题的情况)是静默共享支持对象使该类的用户难以确保该对象在特定点“死”。 例如,对象可能是网络连接,出于安全原因,用户需要知道连接已关闭。

      如果连接包装器是可复制的并且在实例之间共享连接,那么用户必须研究实例可能流经的路径,以确保无法将副本保存在意外的地方并保持连接打开。

      另一方面,如果包装器是不可复制的,则只有一个实例可以确保已死。引用仍然可以分发,但是一旦原始包装器死掉,用户可以确定支持对象也死了。

      如果用户想要拥有共享副本,他们总是可以使用 shared_ptr 再次恢复它。但这一次是他们的决定。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多