Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a part of class name as macro?

Tags:

c++

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;
}
like image 729
ggrr Avatar asked Apr 28 '15 09:04

ggrr


People also ask

Can we define a macro inside a class?

You can use it inside a function, but it is not scoped to the function. So, in your example, the second definitions of a macro will be a redefinition and generate an error. You need to use #undef to clear them first.

What is correct way to define macro?

A macro is a piece of code in a program that is replaced by the value of the macro. Macro is defined by #define directive. Whenever a macro name is encountered by the compiler, it replaces the name with the definition of the macro.

What does ## mean in macro?

The double-number-sign or token-pasting operator (##), which is sometimes called the merging or combining operator, is used in both object-like and function-like macros. It permits separate tokens to be joined into a single token, and therefore, can't be the first or last token in the macro definition.

What is __ function __ in C++?

(C++11) The predefined identifier __func__ is implicitly defined as a string that contains the unqualified and unadorned name of the enclosing function. __func__ is mandated by the C++ standard and is not a Microsoft extension.


2 Answers

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

like image 147
TartanLlama Avatar answered Oct 13 '22 16:10

TartanLlama


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.

like image 35
Petr Avatar answered Oct 13 '22 17:10

Petr