Reputation: 805
I’m writing for practice a C++ wrapper over libusb. I want my API to fully hide the implementation of the underlying lib: the user should not even know I’m actually using libusb. So I created two classes: Context
and Device
. A Device
is created from a Context
using the open
method.
So:
//--- context.hpp -------------------
class Context final
{
public:
Context();
~Context();
std::unique_ptr<Device> open(uint16_t vid, uint16_t pid);
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//--- context.cpp -------------------
struct Context::Impl
{
libusb_context* ctx;
};
Context::Context()
: impl(std::make_unique<Impl>())
{ /* ... */ }
//--- device.hpp -------------------
class Device final
{
public:
Device();
~Device();
private:
struct Impl;
std::unique_ptr<Impl> _impl;
};
//--- device.cpp -------------------
struct Device::Impl
{
libusb_device_handle* handle;
}
Device::Device()
: _impl(std::make_unique<Impl>())
{}
Now the question is: how do I implement the open
method? I need to call libusb_open_device_with_vid_pid
in the implementation which will take the libusb_context*
of my Context::Impl
and store the handle in Device::Impl
. Here are options I thought:
Device
taking a pointer to Context
, but the ctor of Device
does not have access to the libusb_context*
pointer to call the function;Context::Impl
in a header and make Device
a friend of Context
: this sounds ugly though;Device
that takes a libusb_context*
pointer: but I’d be breaking the encapsulation, the user should’nt see the libusb details;native_handle
method to Context
so I can do number one and get the implementation pointer but causing the same problem as number three.open
but how do I initialize my Device
with the libusb_device_handle*
I get? I can’t have a ctor of Device
taking such a pointer, breaking the encapsulation.Upvotes: 2
Views: 122
Reputation: 4325
The friend
-based solution is in fact the cleanest, it's what friend
was designed for.
// device.hpp
class Device final
{
public:
~Device();
private:
Device();
struct Impl;
std::unique_ptr<Impl> impl;
friend class Context;
};
// device.cpp
struct Device::Impl
{
libusb_device_handle* handle;
Impl() : handle(nullptr) {}
}
Device::Device() : impl(new Device::Impl()) {}
std::unique_ptr<Device> Context::open(uint16_t vid, uint16_t pid) {
std::unique_ptr<Device> result(new Device());
result->impl->handle = libusb_open_device_with_vid_pid(vid, pid);
return result;
}
Note that you can actually return Device
rather than std::unique_ptr<Device>
and treat all your bindings as value objects to avoid the extra level of indirection.
EDIT: another option is to use a void pointer. This removes the friend declaration at the expense of introducing a potentially unsafe cast.
// device.hpp
class Device final
{
public:
~Device();
private:
Device(void *handle);
struct Impl;
std::unique_ptr<Impl> impl;
};
// device.cpp
struct Device::Impl
{
libusb_device_handle* handle;
Impl(void *handle)
: handle(static_cast<libusb_device_handle*>(handle)) {}
}
Device::Device(void *handle)
: impl(new Device::Impl(handle)) {}
std::unique_ptr<Device> Context::open(uint16_t vid, uint16_t pid) {
void *handle = libusb_open_device_with_vid_pid(vid, pid);
std::unique_ptr<Device> result(new Device(handle));
return result;
}
Upvotes: 3