Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to instantiate different object types according to value? [closed]

Tags:

c++

I'll simplify my problem of hundreds of classes to a count of two and try to explain what I mean:

class Base {
};
class A: public Base {
};
class B: public Base{
};

static Base* foo (int bar){

    switch (bar) {
        case 0:
            return new A();
            break;              
        case 1:
            return new B();
            break;
        default:
            return new Base();
    }
}

I want to instantiate objects according to the value of bar. I just feel switch-case isn't the best way in C++ to do so for way more inheritors of Base .

Edit: going with the std::map approach I came up with this:

struct Dictionary {
    typedef Base* (Dictionary::*FunctionPointer)(void);
    std::map <int, FunctionPointer> fmap;

    Dictionary() {
        fmap.insert(std::make_pair(0, new A()));
        fmap.insert(std::make_pair(1, new B()));
    }

    Base* Call (const int i){
        FunctionPointer fp = NULL;
        fp = fmap[i];
        if (fp){
            return (this->*fp)();
        } else {
            return new Base();
        }
    }
};

static Dictionary dictionary;
like image 358
dos Avatar asked May 13 '13 08:05

dos


1 Answers

A lot depends on the circumstances, but the most frequent solution is probably to use a static instance of a map to factory functions. If the key type of the map is a small integer value, as in your example, the "map" can be nothing more than a C style array:

static Base*
foo( int bar )
{
    static Base* (*factories[])() = [ &aFactory, &bFactory ];
    return bar >= 0 && bar < size( factories )
        ? (*factories[bar])()
        : baseFactory();
}

More generally, you can use an std::map (to discriminate on any imaginable type), and you can map to static instances of factory objects, rather than factory functions, if different keys should result in the same type, but with different arguments.

EDIT:

Some suggestions to improve your Dictionary::Call function:

Base* Dictionary::Call( int i ) const
{
    std::map<int, FunctionPointer>::const_iterator
                        entry = fmap.find( i );
    return entry == fmap.end()
        ? new Base()
        : (this->*(entry->second))();
}

I've made the function const, since it doesn't modify anything, and most impprtantly, I use std::map<>::find, to avoid inserting extra entries into the map if the object isn't already there.

And since I'm adding the const, you'll have to update the typedef:

typedef Base* (Dictionary::*FunctionPointer)() const;

Another suggestion: unless the factory functions need access Dictionary, make them static. The syntax is a lot simpler (and it will probably also improve performance). static changes the typedef again:

Also: in the constructor, new A() is not a function constructing a new object. There may be something to facilitate this in C++11 (between lambda and std::function), but otherwise, you'll still have to write each of the factory functions by hand. Or you could use a template:

template <typename Target>
Base* construct() const
{
    return new Target();
}

Dictionary()
{
    fmap.insert( std::make_pair( 0, &Dictionary::construct<A> ) );
    //  ...
}

Or if you make them static:

typedef Base* (*FunctionPointer)();

//  ...
template <typename Target>
static Base* construct()
{
    return new Target();
}

Base* Dictionary::Call( int i ) const
{
    std::map<int, FunctionPointer>::const_iterator
                        entry = fmap.find( i );
    return entry == fmap.end()
        ? new Base()
        : (*entry->second)();
}

You'll notice how the static simplifies the declarations (and the function call through the pointer—your pointer to member function has become a simple pointer to function).

like image 78
James Kanze Avatar answered Oct 31 '22 01:10

James Kanze