обертка — Стратегия оборачивания нескольких библиотек в Stack Overflow

У меня есть класс Foo, который я не реализую напрямую, но обернуть внешние библиотеки (например, FooXternal1 или же FooXternal2 )
Один из способов сделать это — использовать директивы препроцессора как

#include "config.h"#include "foo.h"#ifdef _FOOXTERNAL1_WRAPPER_
//implementation of class Foo using FooXternal1
#endif

#ifdef _FOOXTERNAL2_WRAPPER_
//implementation of class Foo using FooXternal2
#endif

и config.h используется для определения этих флагов препроцессора (_FOOXTERNAL1_WRAPPER_ а также _FOOEXTERNAL2_WRAPPER_).
У меня сложилось впечатление, что сообщество программистов на С ++ не одобряет его, поскольку оно использует директивы препроцессора, его трудно отлаживать и т. Д. Кроме того, оно не допускает параллельного существования обеих реализаций.

Я думал о создании Foo базовый класс и наследование от него, чтобы позволить обеим реализациям существовать параллельно друг с другом. Но я столкнулся с двумя проблемами:

  1. Чистые виртуальные функции: cannot instatiate an object of type 'Foo', который мне нужен во время использования.
  2. Виртуальные функции рискуют запустить объект без (правильной) реализации.

Я что-то пропустил? Есть ли более чистый способ сделать это?

РЕДАКТИРОВАТЬ : Подводя итог, существует 3 (.5 ?!) способа сделать обертывание — 2 (.5) предоставлены icepack, а последний — Сергеем
1- Используйте заводские методы
2- Используйте директивы препроцессора
2.5. Используйте make-файл или IDE для эффективного выполнения работы директив препроцессора.
3.5- Используйте шаблоны, предложенные Sergay

Я работаю над встроенной системой, где ресурсы ограничены, я решил использовать template<enum = default_library>, со специализацией шаблонов. Это легко понять для последующих пользователей; по крайней мере, это то, что я думаю

1

Решение

Если все имена методов внешних реализаций похожи, вы можете использовать шаблоны. Пусть внешние реализации выглядят так:

class FooX1
{
public:
void Met1()
{
std::cout << "X1\n";
}
};

class FooX2
{
public:
void Met1()
{
std::cout << "X2\n";
}
};

Тогда вы можете использовать несколько вариантов.

Вариант 1 Вы можете объявить член типа шаблона и обернуть все вызовы во внешнюю реализацию, даже с некоторыми подготовками перед вызовом. Не забудьте удалить impl в ~Foo деструктор.

template<typename FooX>
class FooVariant1
{
public:
FooVariant1()
{
impl=new FooX();
}

void Met1Wrapper()
{
impl->Met1();
}
private:

FooX *impl;
};

Использование:

FooVariant1<FooX1> bar;
bar.Met1Wrapper();

Вариант 2. Вы можете наследовать от параметра шаблона. В этом случае вы не объявляете никаких членов, а просто вызываете методы реализации по их именам.

template<typename FooX>
class FooVariant2 : public FooX
{
};

Использование:

FooVariant2<FooX1> bar;
bar.Met1();

Недостатком использования шаблонов является то, что нет простого способа изменить реализации во время выполнения. Но взамен вы получаете гораздо более оптимальный код, потому что типы генерируются во время компиляции и нет таблицы виртуальных функций, которые могут замедлить работу программы.

2

Другие решения

Если вы хотите, чтобы 2 реализации сосуществовали во время выполнения, интерфейс — это путь (например, вы можете использовать шаблон проектирования фабричного метода для создания экземпляра конкретного объекта, как предложено @ n.m.).

Если вы можете решить во время компиляции, какая реализация вам нужна, у вас есть несколько вариантов:

  • Все еще используйте интерфейс. Это позволит легко переходить, если в будущем вам понадобятся обе реализации во время выполнения.

  • Используйте директивы препроцессора. Здесь нет ничего плохого в том, что касается C ++. Это чисто вопрос дизайна.

  • Поместите реализации в разные файлы и настройте компилятор для компиляции любого из них в соответствии с настройками — это на самом деле похоже на использование директив препроцессора, но оно чище и не добавляет мусора в ваш код (поскольку флаги находятся в решении / make-файле / все, что использует ваш компилятор).

2

Единственное, на что я нахмурился — это включить обе реализации в один и тот же исходный файл. Это может запутать. Иначе, это одна из вещей, которые хорошо подходят для флагов препроцессора, особенно если вы не связываете обе библиотеки одновременно. Это как поддержка нескольких операционных систем. Обеспечьте согласованный интерфейс во всех случаях и скрывайте детали реализации где-либо еще.

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

#include "Foo.h"
#if defined _FOOXTERNAL1_WRAPPER_
#include "Foo_impl1.cpp"#elif defined _FOOXTERNAL2_WRAPPER_
#include "Foo_impl2.cpp"#else
#error "Warn about a missing define here"#endif

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

1

Держать Foo Аннотация. Обеспечить заводской метод

Foo* MakeFoo();

который выделяет новый объект любого типа FooImpl1 или же FooImpl2и возвращает свой адрес.

Википедия о фабричном методе.

1
По вопросам рекламы [email protected]