ggrr
ggrr

Reputation: 7867

How to define a part of class name as macro?

For example, if I have so many class that have same prefix in same platform:

in android:

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

in iOS:

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

is it possible to define a part of class name like that:

#define PREFIX Android

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

instead of:

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

Upvotes: 16

Views: 4604

Answers (4)

Jahid
Jahid

Reputation: 22448

You can't. PREFIXPrinter(); will be a function named PREFIXPrinter(); not AndroidPrinter();

You can do it like this:

#define PREFIX(a) Android##a

Usage:

PREFIX(Printer)();

It will call AndroidPrinter()

Upvotes: 9

Petr
Petr

Reputation: 10007

You can not have exactly the syntax you want. However, you can do some more preprocessor magic to have it working:

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

However, it would be a better way to use another approach, such as namespace suggested in the comments, or even better an abstract factory, something like this:

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

(Even better will be to use auto_ptrs or even unique_ptrs.)

(You can also make your factory a singleton if you want.)

UPDATE:

Another approach is to have two independent factory classes and just typedef to use one of them:

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

See also TartanLlama's answer for an more advanced version of this approach, including static functions to avoid passing a pointer around.

This approach has an advantage that everything is resolved at compile time and no virtual calls are made.

However, I do not think that virtual functions will be a real bottleneck, because being a factory it will not be called many times. I think your usage pattern will be something on the lines of

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

and most of the time will be spend in the doSomething() call, so getFoo() virtual call overhead will not be a bottleneck.

At the same time, this approach has a disadvantage that it lacks proper inheritance structure and thus makes extension more difficult (and static functions would make extension even more difficult).

Imagine you will want to have a bit different factories for Android phones and Android tablets. With inheritance, you will simply have a basic AndroidBaseFactory class that has common logic and two subclasses for phones and tablets replacing only what you need. Without inheritance you will have to copy all common functionality (even if it is just a return new AndroidFoo(); call) in both factory classes.

Also, unit testing and mocking is simpler with inheritance and pointers being passes around.

In fact, many of arguments of "singleton vs a bunch of static functions" apply here too.

Upvotes: 11

TartanLlama
TartanLlama

Reputation: 65770

You could use a static factory pattern with some template specialization for this. You don't really need a fully-fledged abstract factory, because you won't be switching platform halfway through execution. This way, you can work out which factory implementation to use at compile-time with no overhead (assuming that your factory methods are inlined and just forward to operator new).

Define a couple of dummy structs to act as platform identifiers:

struct Android{};
struct IOS{};

Write some factory implementations based on those dummy structs:

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(); }
};

Work out what platform to use based on some macro:

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

Then typedef the instance for the platform you are working on:

using ComponentFactory = ConcreteComponentFactory<Platform>;

Now we can create instances of our objects and forget about what platform we are on:

Writer *a = ComponentFactory::CreateWriter();

If we need to know what platform we are on at some point, we can just refer to the Platform typedef.

This approach is pretty flexible and far more type-safe than the macro-based version you have.

Demo

Upvotes: 18

Lajos Arpad
Lajos Arpad

Reputation: 77073

This is not possible, but you can do other things with similar generalizing effect.

  1. As @Biffen pointed out, you can use namespaces

  2. As @Petr pointed out, you can use an abstract factory

  3. As @Petr pointed out in his answer, you can do some preprocessor magic

  4. You can use reflection

Upvotes: 1

Related Questions