C++ 句柄 class 抽象
C++ handle class abstraction
我正在尝试使用 classes 来抽象显式创建和销毁原始句柄。实际句柄存储为私有 class 成员(这样用户就不会与较低级别的细节进行交互),它在构建时创建并在销毁时销毁。是否有某种设计模式可以帮助实现以下代码试图实现的目标?
请注意,可能会有大量 class 相互依赖,因此用大量 friend 语句污染每个 class 既乏味又不好。
#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;
}
Is there a design pattern of some sorts that could help achieve what the code below is trying to accomplish?
是的。称为Resource Acquisition Is Initialization,简称RAII。您的第一次尝试方向正确,但可能不完整。可能需要担心的是,通常 多次“销毁”原始句柄是错误的。因此,您应该建立一个“class 不变量”作为每个成员函数的 post 条件,class 的两个实例都不会拥有相同的原始句柄。您的 classes 目前违反了此类不变量。考虑复制实例时会发生什么。有一个经验法则称为五法则(以前是三法则),这将有助于建立该不变性。
关于私密访问和回避朋友,一个好的解决方案是提供一个public getter:
class A
{
public
A_Handle get_handle() { return handle; }
成员仍然是封装的,class 的用户将无法破坏不变性,因为他们无法修改它。
您不必为此推出自己的解决方案。相反,您可以将 std::unique_ptr
与自定义删除器一起使用,该删除器知道如何在 unique_ptr
超出范围时销毁句柄。
这是一个例子,使用 FILE *
作为 'handle':
#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
的类似技巧。
我正在尝试使用 classes 来抽象显式创建和销毁原始句柄。实际句柄存储为私有 class 成员(这样用户就不会与较低级别的细节进行交互),它在构建时创建并在销毁时销毁。是否有某种设计模式可以帮助实现以下代码试图实现的目标?
请注意,可能会有大量 class 相互依赖,因此用大量 friend 语句污染每个 class 既乏味又不好。
#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;
}
Is there a design pattern of some sorts that could help achieve what the code below is trying to accomplish?
是的。称为Resource Acquisition Is Initialization,简称RAII。您的第一次尝试方向正确,但可能不完整。可能需要担心的是,通常 多次“销毁”原始句柄是错误的。因此,您应该建立一个“class 不变量”作为每个成员函数的 post 条件,class 的两个实例都不会拥有相同的原始句柄。您的 classes 目前违反了此类不变量。考虑复制实例时会发生什么。有一个经验法则称为五法则(以前是三法则),这将有助于建立该不变性。
关于私密访问和回避朋友,一个好的解决方案是提供一个public getter:
class A
{
public
A_Handle get_handle() { return handle; }
成员仍然是封装的,class 的用户将无法破坏不变性,因为他们无法修改它。
您不必为此推出自己的解决方案。相反,您可以将 std::unique_ptr
与自定义删除器一起使用,该删除器知道如何在 unique_ptr
超出范围时销毁句柄。
这是一个例子,使用 FILE *
作为 'handle':
#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
的类似技巧。