Reputation: 325
I am trying to abstract the explicit creation and destruction of raw handles with the usage of classes. The actual handle is stored as a private class member (so that the user doesn't interact with the lower-level details), which is created on construction and destroyed on destruction. Is there a design pattern of some sorts that could help achieve what the code below is trying to accomplish?
Note that it is possible for there to be loads of classes interdependent to each other, therefore it would be both tedious and bad practice to pollute each class with lots of friend statements.
#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;
}
Upvotes: 0
Views: 252
Reputation: 25388
You don't have to roll your own solution for this. Instead, you can use std::unique_ptr
with a custom deleter which knows how to destroy the handle when the unique_ptr
goes out of scope.
Here's an example, using FILE *
as a '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
}
}
If your handle is not a pointer type, you can cast it to and from a void *
(most handles fit in a void *)
. For example:
#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
}
}
You can, of course, define type aliases for those complicated looking templates to make the code neater.
std::unique_ptr
has some nice features, including a deleted copy constructor and a viable move constructor. Also, you can work a similar trick with std::shared_ptr
if you need shared ownership semantics (aka reference counting).
Upvotes: 1
Reputation: 238401
Is there a design pattern of some sorts that could help achieve what the code below is trying to accomplish?
Yes. It is called Resource Acquisition Is Initialization, or RAII for short. Your first attempt is in the right direction, but it is likely incomplete. A thing to potentially be concerned about is that typically it is an error to "destroy" a raw handle multiple times. Hence, you should establish a "class invariant" that as a post condition of every member function, no two instances of the class own the same raw handle. Your classes currently violate such invariant. Consider what happens when you make a copy of an instance. There is a rule of thumb called rule of five (previously rule of three) that will help establishing that invariant.
As for the private access and avoiding friends, a good solution is to provide a public getter:
class A
{
public
A_Handle get_handle() { return handle; }
The member is still encapsulated and the users of the class will be unable to break the invariant since they cannot modify it.
Upvotes: 1