Скрыть тип класса в заголовке - PullRequest
2 голосов
/ 19 августа 2010

Я не уверен, возможно ли это вообще, но здесь идет речь:

У меня есть библиотека, интерфейс которой, в лучшем случае, сложен. К сожалению, это не только сторонняя библиотека (и слишком большая, чтобы ее переписать), я использую несколько других библиотек, которые зависят от нее. Так что интерфейс должен оставаться таким, какой он есть.

Чтобы решить эту проблему, я пытаюсь по существу обернуть интерфейс и объединить все интерфейсы зависимостей в меньшее количество логических классов. Эта часть идет хорошо и прекрасно работает. Большинство классов-оболочек содержат указатель на объект одного из исходных классов. Вот так:

class Node
{
public:
    String GetName()
    {
        return this->llNode->getNodeName();
    }

private:
    OverlyComplicatedNodeClass * llNode; // low-level node
};

Моя единственная проблема - это второстепенный вопрос. Помимо упрощения интерфейса, я бы хотел убрать требование связывания с исходными заголовками / библиотеками.

Это первая трудность. Как я могу обернуть классы таким образом, что нет необходимости включать оригинальные заголовки? Оболочка будет построена как разделяемая библиотека (dll / so), если это делает ее проще.

Исходные классы являются указателями и не используются ни в каких экспортируемых функциях (хотя они используются в нескольких конструкторах).

Я поиграл с несколькими идеями, в том числе с препроцессором:

#ifdef ACCESSLOWLEVEL
#    define LLPtr(n) n *
#else
#    define LLPtr(n) void *
#endif

Что в лучшем случае ужасно. Он делает то, что мне нужно в принципе, но я бы предпочел реальное решение такого рода беспорядка.

Некоторая магия типа указателя работает, пока я не наткнулся на несколько функций, использующих общие указатели (некоторый пользовательский класс SharedPtr<>, обеспечивающий подсчет ссылок) и, что еще хуже, несколько специфических для класса общих указателей, полученных из базовый SharedPtr класс (NodePtr, например).

Можно ли вообще обернуть оригинальную библиотеку таким образом, чтобы для ссылки на мою динамическую библиотеку требовалось включать только мои заголовки? Нет необходимости ссылаться на исходную библиотеку или вызови функции из него, только мои. Единственная проблема, с которой я сталкиваюсь - это используемые типы / классы.

Вопрос может быть не совсем понятным. Я могу попытаться очистить его и добавить больше примеров кода, если это поможет. Я не очень беспокоюсь о каких-либо затратах на производительность или об этом методе, просто сначала пытаюсь заставить его работать (преждевременная оптимизация и все такое).

Ответы [ 2 ]

4 голосов
/ 20 августа 2010

Используйте идиому Pimpl (указатель на реализацию). Как описано, OverlyComplicatedNodeClass - это деталь реализации, касающаяся пользователей вашей библиотеки. Им не нужно знать структуру этого класса или даже его имя.

Когда вы используете идиому Pimpl, вы заменяете указатель OverlyComplicatedNodeClass в своем классе указателем на void. Только вы, автор библиотеки, должны знать, что void* на самом деле OverlyComplicatedNodeClass*. Итак, ваше объявление класса становится:

class Node
{
public:
    String GetName();

private:
    void * impl; 
};

В реализации вашей библиотеки инициализируйте impl указателем на класс, который выполняет реальную работу:

my_lib.cpp

Node::Node()
: impl(new OverlyComplicatedNodeClass)
{
// ...
};

... и пользователям вашей библиотеки не нужно знать, что OverlyComplicatedNodeClass существует.

У этого подхода есть один потенциальный недостаток. Весь код, который использует класс impl, должен быть реализован в вашей библиотеке. Нет, если он может быть встроенным. Является ли это недостатком, во многом зависит от вашего приложения, поэтому судите сами.

В случае вашего класса у вас была реализация GetName() в заголовке. Это должно быть перемещено в библиотеку, как и для всего другого кода, который использует указатель impl.

0 голосов
/ 20 августа 2010

По сути, вам нужен отдельный набор заголовков для каждого использования. Тот, который вы используете для создания вашей DLL, и тот, который содержит только экспортированные интерфейсы, и вообще не упоминает инкапсулированные объекты. Ваш пример будет выглядеть так:

class Node
{
public:
    String GetName();
};

Вы можете использовать операторы препроцессора, чтобы получить обе версии в одном физическом файле, если вы не возражаете против беспорядка.

...