2016-10-07 1 views
2

libusb에서 C++ 래퍼 연습을 위해 글을 쓸 것입니다. 내 API가 기본 lib의 구현을 완전히 숨기고 싶습니다. 사용자가 실제로 libusb를 사용하고 있는지조차 알지 못합니다. 그래서 두 클래스를 만들었습니다 : ContextDevice. Deviceopen 메서드를 사용하여 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으로 전화하여 libusb_context*이 내 Context::Impl이고 핸들을 Device::Impl에 저장해야합니다. 여기에 내가 생각했던 옵션입니다

  1. 내가 Context에 대한 포인터를 복용 Device의 생성자를 만들 수는 있지만, Device의 ctor에 함수를 호출 libusb_context* 포인터에 액세스 할 수 없습니다;
  2. 나는 포인트 번호 1을 따르고 Context::Impl을 헤더에 넣고 DeviceContext의 친구로 만듭니다. 이것은 추한 것처럼 들리지만,
  3. libusb_context* 포인터를 사용하는 Device의 생성자를 만듭니다. 그러나 캡슐화를 해제하려고합니다. 사용자는 libusb 세부 정보를 보지 말아야합니다.
  4. Contextnative_handle 메서드를 추가하여 번호 1을 수행하고 구현 포인터를 얻을 수 있지만 숫자 3과 같은 문제가 발생합니다.
  5. 나는 open에 함수 호출을 만들지 만 Device을 초기화하려면 libusb_device_handle*을 얻으시겠습니까? 캡슐화를 깨고 그런 포인터를 가지고 Device의 ctor을 가질 수 없습니다.
+0

사용자가 * context * 개체를 전혀 볼 필요가 있습니까? 그것은 단지'Device'에 대한 숨겨진 구현 정보일까요? 나는'libusb'에 익숙하지 않지만 문맥 타입 객체는 인스턴스 정보를 유지하기위한'C' 핸들이다. 'C++ '에서 동일한 작업이 클래스 내부에서 종종 수행되는 경우 - 클래스 객체 **는 자체 핸들입니다. – Galik

+0

@Galik 컨텍스트마다 여러 장치가있을 수 있으며 여러 컨텍스트가있을 수 있습니다. –

답변

3

friend 기반 솔루션은 friend 위해 설계되었다 무엇을, 사실 가장 깨끗한입니다. 실제로 std::unique_ptr<Device>보다는 Device을 반환하고 간접 여분의 수준을 피하기 위해 값 개체로 모든 바인딩을 처리 할 수 ​​

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

참고.

EDIT : 다른 옵션은 void 포인터를 사용하는 것입니다. 이렇게하면 잠재적으로 위험한 캐스트를 도입하는 대신 친구 선언이 제거됩니다.

// 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'의 속성 초기화가 잘못된 것 같지 않습니까? 'Context'를 매개 변수로 취하고 함수 호출을하는'Device' ctor에 의해 이루어져야하지 않습니까? 'Device'는 'Context'의 친구 다.또한 포인터 대신 객체를 사용하는 경우 객체를 복사하지 않고 함수를 전달할 수 있습니까 (감각이 없음). 호출자 인수를 이동하여 무효화하지 않고 어떻게 할 수 있습니까? 어쩌면 참조를 전달할 수 있을까요? –

+0

속성을 직접 초기화하지 않으려면 void 포인터를 사용하여 생성자에 libusb 핸들을 전달할 수 있습니다. 나는 대답을 업데이트했다. –

관련 문제