C++ Question

Proper encapsulation with class relations when creating objects

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:


  1. I create a constructor of
    Device
    taking a pointer to
    Context
    , but the ctor of
    Device
    does not have access to the
    libusb_context*
    pointer to call the function;

  2. I follow point number one and put the
    Context::Impl
    in a header and make
    Device
    a friend of
    Context
    : this sounds ugly though;

  3. I create a constructor of
    Device
    that takes a
    libusb_context*
    pointer: but I’d be breaking the encapsulation, the user should’nt see the libusb details;

  4. I add a
    native_handle
    method to
    Context
    so I can do number one and get the implementation pointer but causing the same problem as number three.

  5. I make the function call in
    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.


Answer

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);
}

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.