Как определить часть имени класса как макроса?

Например, если у меня так много классов, которые имеют один и тот же префикс в той же платформе:

в андроиде:

Printer *p=new AndroidPrinter();
Writer *w=new AndroidWriter();
Connector *c=new AndroidConnector();

в iOS:

Printer *p=new IOSPrinter();
Writer *w=new IOSWriter();
Connector *c=new IOSConnector();

Можно ли определить часть имени класса:

#define PREFIX Android

int main(){
    Printer *p=new PREFIXPrinter();
    Writer *w=new PREFIXWriter();
    Connector *c=new PREFIXConnector();
    return 0;
}

вместо:

#define PLATFORM 0

#if PLATFORM==0
#define PRINTER AndroidPrinter
#else
#define PRINTER IOSPrinter
#endif

#if PLATFORM==0
#define WRITER AndroidWriter
#else
#define WRITER IOSWriter
#endif

#if PLATFORM==0
#define CONNECTOR AndroidConnector
#else
#define CONNECTOR IOSConnector
#endif

int main(){
    Printer *p=new PRINTER();
    Writer *w=new WRITER();
    Connector *c=new CONNECTOR();
    return 0;
}

Ответы

Ответ 1

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

Определите пару фиктивных структур для работы в качестве идентификаторов платформы:

struct Android{};
struct IOS{};

Напишите некоторые реализации factory на основе этих фиктивных структур:

template <typename T>
struct ConcreteComponentFactory;

template <>
struct ConcreteComponentFactory<Android>
{
    static Printer *CreatePrinter()
    { return new AndroidPrinter(); }
    static Writer *CreateWriter()
    { return new AndroidWriter(); }
    static Connector *CreateConnector()
    { return new AndroidConnector(); }
};

template <>
struct ConcreteComponentFactory<IOS>
{
    static Printer *CreatePrinter()
    { return new IOSPrinter(); }
    static Writer *CreateWriter()
    { return new IOSWriter(); }
    static Connector *CreateConnector()
    { return new IOSConnector(); }
};

Определите, какую платформу использовать на основе макроса:

#define PLATFORM 0
#if PLATFORM==0
using Platform = Android;
#else
using Platform = IOS;
#endif

Затем typedef экземпляр для платформы, над которой вы работаете:

using ComponentFactory = ConcreteComponentFactory<Platform>;

Теперь мы можем создавать экземпляры наших объектов и забывать о том, на какой платформе мы находимся:

Writer *a = ComponentFactory::CreateWriter();

Если нам нужно знать, на какой платформе мы находимся в какой-то момент, мы можем просто обратиться к Platform typedef.

Этот подход довольно гибкий и гораздо более безопасный для типов, чем версия на основе макросов.

Демо

Ответ 2

У вас не может быть именно тот синтаксис, который вы хотите. Тем не менее, вы можете сделать еще одну магию препроцессора, чтобы она работала:

#define CONCAT_HELPER(a,b) a ## b
#define CONCAT(a,b) CONCAT_HELPER(a,b)

#define x ABC

#define MAKENAME(y) CONCAT(x,y)

int MAKENAME(def); // this defines ABCdef variable

int main() {
    ABCdef = 0;
    return 0;
}

Однако было бы лучше использовать другой подход, например, пространство имен, предложенное в комментариях, или даже лучше абстрактное factory, что-то вроде этого:

class Factory {
public:
    virtual Printer* getPrinter() = 0;
    virtual Writer* getWriter() = 0;
};
class AndroidFactory: public Factory {
public:
    virtual Printer* getPrinter() { return new AndroidPrinter(); }
    virtual Writer* getWriter() { return new AndroidWriter(); }
};
// the same for IOS, or, 
// if you really have such a similar code here, 
// you can make a macros to define a factory

...
int main() {
    #ifdef ANDROID
         Factory* factory = new AndroidFactory();
    #else
         Factory* factory = new IOSFactory();
    #endif
    // now just pass factory pointer everywhere
    doSomething(old, parameters, factory);
}

(Еще лучше использовать auto_ptr или даже unique_ptr s.)

(Вы также можете сделать свой factory синглтон, если хотите.)

UPDATE:

Другим подходом является наличие двух независимых классов factory и просто typedef для использования одного из них:

class AndroidFactory { // no inheritance here
    public:
        Printer* getPrinter() {...} // no virtual here!
        ...
};
class IOSFactory {
    ...
};
#ifdef ANDROID
typedef AndroidFactory Factory;
#else
typedef IOSFactory Factory;
#endif

// note that we pass Factory* here 
// so it will compile and work on both Android and IOS
void doSomething(int param, Factory* factory);

int main() {
    Factory* factory = new Factory(); 
    // and pass factory pointer around
    doSomething(param, factory);
}

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

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

Однако я не думаю, что виртуальные функции станут настоящим узким местом, потому что, будучи factory, он не будет вызываться много раз. Я думаю, что ваш шаблон использования будет чем-то вроде строк

Foo* foo = factory.getFoo();
foo->doSomething();

и большую часть времени будет потрачено на вызов doSomething(), поэтому getFoo() накладные расходы виртуального вызова не будут узким местом.

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

Представьте, что вы захотите иметь несколько разных фабрик для телефонов Android и планшетов Android. С наследованием вы просто будете иметь базовый класс AndroidBaseFactory, который имеет общую логику и два подкласса для телефонов и планшетов, заменяющих только то, что вам нужно. Без наследования вам придется скопировать все общие функции (даже если это всего лишь вызов return new AndroidFoo();) в обоих классах factory.

Кроме того, модульное тестирование и издевательство проще с наследованием и указателями.

Фактически, многие аргументы "singleton vs bunch of static functions" также применяются здесь.

Ответ 3

Вы не можете. PREFIXPrinter(); будет функция с именем PREFIXPrinter(); not AndroidPrinter();

Вы можете сделать это следующим образом:

#define PREFIX(a) Android##a

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

PREFIX(Printer)();

Он выберет AndroidPrinter()

Ответ 4

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

  • Как отметил @Biffen, вы можете использовать namespaces

  • Как отметил @Petr, вы можете использовать abstract factory

  • Как отметил в своем ответе @Petr, вы можете сделать магию препроцессора

  • Вы можете использовать reflection