2016-10-07 151 views
2

我正在爲實踐編寫一個C++包裝器,用於libusb。我想讓我的API完全隱藏底層庫的實現:用戶甚至不應該知道我實際上在使用libusb。所以我創建了兩個類:ContextDevice。 A Device使用open方法從Context創建。創建對象時正確封裝類關係

所以:

//--- 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>()) 
{} 

現在的問題是:我該如何實現open方法?我需要在執行過程中調用libusb_open_device_with_vid_pid,這將執行我的Context::Impllibusb_context*並將句柄存儲在Device::Impl中。下面是我想選擇:

  1. 我創建的Device一個構造函數採用指針來Context,但Device的構造函數沒有訪問libusb_context*指針調用的函數;
  2. 我遵循第一點,並將Context::Impl放在標題中並使Device成爲Context的朋友:儘管這聽起來很醜陋,
  3. 我創建了一個Device的構造函數,它需要一個libusb_context*指針:但我會打破封裝,用戶應該不會看到libusb的細節;
  4. 我添加一個native_handle方法到Context,所以我可以做第一個並獲得實現指針,但導致與第三個相同的問題。
  5. 我在open中進行函數調用,但是如何將Device初始化爲我得到的libusb_device_handle*?我不能有一個Device ctor這樣的指針,打破封裝。
+0

用戶是否需要查看* context *對象?難道這只是一個隱藏的'Device'的實現細節?我對'libusb'不熟悉,但經常上下文類型的對象是維護實例信息的'C'句柄。在'C++'中,相同的工作通常在類的內部完成 - 類對象**是**自己的句柄。 – Galik

+0

@Galik每個上下文可以有多個設備,並且可以有多個上下文。 –

回答

3

friend基於解決方案實際上是最乾淨的,這是什麼friend是專爲。

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

注意,你實際上可以返回Device,而不是std::unique_ptr<Device>和對待所有綁定的值對象,以避免間接的額外水平。

編輯:另一種選擇是使用無效指針。這會以犧牲引入潛在的不安全轉換爲代價去除朋友聲明。

// 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; 
} 
+0

儘管它已經使用友誼,它不會更好'返回std :: unique_ptr (新設備(libusb_open_device_with_vid_pid(vid,pid)));'? – bipll

+0

初始化'Context'類中'Device'的屬性感覺不對,不是嗎?不應該由'Device' ctor以'Context'作爲參數來完成並調用函數嗎?友情會相反:'Device'是'Context'的朋友。另外,如果我使用的是對象而不是指針,那麼如何將對象傳遞給函數而不復制它(沒有意義),並且不通過移動它來使調用者參數無效?也許通過參考? –

+0

如果您不想直接初始化屬性,則可以使用void指針將libusb句柄傳遞給構造函數。我更新了答案。 –