Как я могу скрыть API, который включает пользовательские определения типов в своем интерфейсе за идиомой PIMPL? - PullRequest
0 голосов
/ 03 мая 2019

У меня очень неприятная ситуация, когда я пытаюсь отделить свое программное обеспечение от зависимости времени компиляции от предоставляемого поставщиком API. Типичный способ сделать это - реализовать класс «обертка» вокруг API, который использует идиому PIMPL, чтобы скрыть предоставляемый поставщиком API полностью за непрозрачным указателем.

Например ...

wrapper.h:

#pragma once

#include <memory>

class Wrapper {
public:
    Wrapper();
    ~Wrapper();

    // Function that uses vendor API in its implementation
    void Function1();
private:
    struct Impl;
    std::unique_ptr<Impl> m_impl;
};

wrapper.cpp

#include "wrapper.h"
#include <memory>
#include <vendor/api.h>

struct Wrapper::Impl {
    void Function1() {

        // Vendor API call hidden entirely within the private implementation
        VendorApiFunction1();
    }
};

Wrapper::Wrapper() : m_impl(std::make_unique<Impl>()) {}
Wrapper::~Wrapper() = default;

// Forward call to the private implementation
Wrapper::Function1() {
    m_impl->Function1();
}

Проблема заключается в том, что предоставляемый поставщиком API-интерфейс определяет некоторые глобальные typedef, которые он использует в своем интерфейсе. Что делает объявление Function1() в wrapper.h похожим на это:

void Function1(CUSTOM_API_TYPE input);

Затем эти входные данные передаются в реализацию, где в конечном итоге передаются функции API, предоставляемой поставщиком.

Это создает очень неприятную проблему ... Так как typedef s не может быть объявлено вперед, теперь я вынужден добавить #include <vendor/api.h> к вершине wrapper.h, чтобы сделать CUSTOM_API_TYPE доступным. Добавление заголовка вендора к wrapper.h затем накладывает зависимость времени компиляции от API, предоставленного вендором, для всех клиентов wrapper.h, и это именно то, что я использовал для обозначения PIMPL, чтобы избежать ...

Есть ли какой-нибудь стандартный способ справиться с такой ситуацией?

1 Ответ

0 голосов
/ 03 мая 2019

Я бы завернул CUSTOM_API_TYPE в свой собственный тип и перевел бы его в свою обертку.

Это может создать много лишнего кода, но это путь, если вы хотите скрыть детали реализации.

Пример:

WRAPPER.H

#pragma once

#include <memory>

struct MyCustomInput {
  int param1;
  int param2;
};

class Wrapper {
public:
    Wrapper();
    ~Wrapper();

    // Function that uses vendor API in its implementation
    void Function1(MyCustomInput input);
private:
    struct Impl;
    std::unique_ptr<Impl> m_impl;
};

WRAPPER.CPP

#include "wrapper.h"
#include <memory>
#include <vendor/api.h>

struct Wrapper::Impl {
    void Function1(CUSTOM_API_TYPE implType) {

        // Vendor API call hidden entirely within the private implementation
        VendorApiFunction1(implType);
    }
};

Wrapper::Wrapper() : m_impl(std::make_unique<Impl>()) {}
Wrapper::~Wrapper() = default;

// Forward call to the private implementation
Wrapper::Function1(MyCustomInput input) {
  CUSTOM_API_TYPE implType;
  // That example is a 1:1 conversion of parameters, but you can ease the
  // programmer's life a little bit by hiding the API details
  implType.param1 = input.param1;
  implType.param2 = input.param2;
  m_impl->Function1(implType);
}
...