【问题标题】:C++ handle class abstractionC++句柄类抽象
【发布时间】:2021-06-11 22:40:57
【问题描述】:

我正在尝试使用类来抽象原始句柄的显式创建和销毁。实际句柄存储为私有类成员(这样用户就不会与较低级别的细节交互),它在构造时创建并在销毁时销毁。是否有某种设计模式可以帮助实现下面的代码试图实现的目标?

请注意,可能存在大量相互依赖的类,因此用大量友元语句污染每个类既乏味又不好。

#include <memory>

// Handle types may vary
typedef uint32_t A_Handle;
typedef uint32_t B_Handle;
typedef int64_t C_Handle;

extern void createA(A_Handle*);
extern void destroyA(A_Handle);
extern void createB(B_Handle*);
extern void destroyB(B_Handle);
extern void createC(C_Handle*, A_Handle, B_Handle);
extern void destroyC(C_Handle, A_Handle, B_Handle);

class A
{
private:
    A_Handle handle_;
public:
    A()
    {
        createA(&handle_);
    }

    ~A()
    {
        destroyA(handle_);
    }

    A(const A&) = delete;

    A& operator=(const A&) = delete;
};

class B
{
private:
    B_Handle handle_;
public:
    B()
    {
        createB(&handle_);
    }

    ~B()
    {
        destroyB(handle_);
    }

    B(const B&) = delete;

    B& operator=(const B&) = delete;
};

class C
{
private:
    C_Handle handle_;
public:
    std::shared_ptr<A> a;
    std::shared_ptr<B> b;

    C(const std::shared_ptr<A>& a, const std::shared_ptr<B>& b)
        : a(a)
        , b(b)
    {
        // Error a->handle_ and b->handle_ is private
        createC(&handle_, a->handle_, b->handle_);
    }

    ~C()
    {
        // Error a->handle_ and b->handle_ is private
        destroyC(handle_, a->handle_, b->handle_);
    }

    C(const C&) = delete;

    C& operator=(const C&) = delete;
};

// ...

int main()
{
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    std::shared_ptr<C> c = std::make_shared<C>(a, b);

    // ...

    return EXIT_SUCCESS;
}

【问题讨论】:

  • 推荐观察AB类中的Rule of Three (or five)
  • 大量的“extern”是否支持C接口?
  • @user4581301 或零规则。由于这个类处理所有权,我们至少删除了不需要的成员(看起来这些类不应该是可复制的)
  • @user4581301 是的,你说得对,类应该是不可复制的(我已经用删除的复制构造函数和赋值运算符更新了代码)。但我不明白零规则是如何工作的,因为句柄永远不会被破坏,因为没有析构函数。
  • 零规则不适用于编写的类,但是一旦我有一个需要由多个类使用的特殊处理的资源,我将把该资源包装在一个完全符合 RAII 的类中ABC 类将使用该类来遵守零规则。让观察 3 和 5 的班级尽可能靠近他们保护的资源,以便其他班级尽可能愚蠢。

标签: c++ oop design-patterns friend handle


【解决方案1】:

是否有某种设计模式可以帮助实现下面的代码试图实现的目标?

是的。称为Resource Acquisition Is Initialization,简称RAII。您的第一次尝试是正确的方向,但它可能是不完整的。可能需要担心的是,通常多次“销毁”原始句柄是错误的。因此,您应该建立一个“类不变量”,作为每个成员函数的后置条件,该类的任何两个实例都不会拥有相同的原始句柄。您的课程目前违反了这种不变量。考虑复制实例时会发生什么。有一个经验法则称为五法则(以前的三法则),这将有助于建立该不变量。

对于私有访问和回避朋友,一个好的解决方案是提供一个公共的getter:

class A
{
public
    A_Handle get_handle() { return handle; }

成员仍然被封装,类的用户将无法破坏不变量,因为他们无法修改它。

【讨论】:

  • 感谢您的明确解释,我想 getter 将是唯一的选择。我仍然不确定这是否是一个好主意,因为这本书 C++ coding standards 第 42 章(第 74 页)指出应该避免放弃内部结构,因为虽然他们可能无法修改手柄,但他们仍然可以使用手柄更改该句柄指向的对象(例如通过手动销毁它)。
  • @MaximV 记录如果用户执行此类操作,则行为未定义。
  • 我认为您在说“可能需要关注的一件事是,通常多次“破坏”原始句柄是错误的”,这就是为什么我将实例包装在共享指针中:这样构造和销毁只发生一次(每个 std::make_shared)。
【解决方案2】:

您不必为此推出自己的解决方案。相反,您可以将std::unique_ptr 与自定义删除程序一起使用,该删除程序知道当unique_ptr 超出范围时如何销毁句柄。

这是一个示例,使用FILE * 作为“句柄”:

#include <cstdio>
#include <memory>

int main ()
{
    FILE *f = std::fopen ("myfile", "r");
    if (f)
    {
        std::unique_ptr <FILE, decltype (&std::fclose)> upf (f, std::fclose);
        // do things with the open file
        // ...
        // file will be closed here, when upf goes out of scope
    }
}

如果您的句柄不是指针类型,您可以将其转换为void *(大多数句柄适合void *)。例如:

#include <sys/stat.h>
#include <fcntl.h>
#include <cstdint>
#include <memory>

int main ()
{
    int fd = open ("myfile", O_RDONLY);
    if (fd >= 0)
    {
        std::unique_ptr <void, void (*) (void *)> upfd
            ((void *) (uintptr_t) fd, [] (void *fd) { close ((int) (uintptr_t) fd); });
        // do things with the open file
        // ...
        // file will be closed here, when upfd goes out of scope
    }
}

当然,您可以为那些看起来很复杂的模板定义类型别名,以使代码更整洁。

std::unique_ptr 有一些不错的功能,包括删除的复制构造函数和可行的移动构造函数。此外,如果您需要共享所有权语义(也称为引用计数),您可以使用 std::shared_ptr 使用类似的技巧。

【讨论】:

  • 谢谢,这意味着现在我可以使用自定义删除函数将原始句柄包装在unique_ptr 中,类现在可以保存在其中,因此它们现在可以遵循零规则。但是,这并不能完全回答关于如何在保持尽可能多的封装性的同时从其他类访问私有句柄的问题。
  • 嗯,没错,std::unique_ptr 并没有像您编写自己的类那样保护您的句柄。 OTOH,它功能齐全,免费提供。
猜你喜欢
  • 1970-01-01
  • 2013-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多