2015-08-06 4 views
1

Как я могу перебирать список типов без создания экземпляра каждого типа?Итерация через список типов

В моем примере у меня есть родительский класс с методом getByName, который возвращает экземпляр дочернего класса. Метод getByName полностью разрушен, так как вы не можете создать массив typedefs. Каков наилучший способ сделать эту работу?

Одним из решений является создание массива указателей name, но это будет беспорядочно, если есть несколько переменных (больше, чем просто name), с которыми я хочу проверить.

Я в основном хочу чистое решение, которое использует цикл, а не ряд утверждений if.

#include <string> 

struct Number { 
    Number(const std::string &name) :name(name) {} 

    // fix me! 
    static Number* getByName(const std::string &name) { 
     typedef types[] = { 
      One, 
      Two, 
      Three, 
     } 
     for (int i = 0; i < 3; ++i) { 
      if (name == types[i]::name) 
       return new types[i](); 
     } 
     return nullptr; 
    } 
    const std::string name; 
}; 

struct One :Number { 
    One() :Number(name) {} 
    const static std::string name; 
}; 

struct Two :Number { 
    Two() :Number(name) {} 
    const static std::string name; 
}; 

struct Three :Number { 
    Three() :Number(name) {} 
    const static std::string name; 
}; 

const std::string One::name = "one"; 
const std::string Two::name = "two"; 
const std::string Three::name = "three"; 
+1

Я рекомендую использовать структуру имен и указателей функций; или 'std :: map' с именем в качестве ключа и указателем функции в качестве значения. –

+1

Поиск в StackOverflow или в Интернете для «заводской реализации C++». –

ответ

1

Вы не можете хранить типы непосредственно осмысленным образом; Однако, вы можете хранить указатели на заводские функции, которые должны быть одинаково полезны:

#include <iostream> 
#include <string> 
#include <map> 

using namespace std; 

struct Number; 

Number* oneCreator(); 
Number* twoCreator(); 
Number* threeCreator(); 

struct Number { 
    typedef Number* (*creatorFP)(); 
    typedef map<string, creatorFP> CreatorMap; 

    static Number* getByName(const string &name) { 
     // maybe initialise this map somewhere else 
     CreatorMap creators; 
     creators.insert(make_pair(string("one"), &oneCreator)); 
     creators.insert(make_pair(string("two"), &twoCreator)); 
     creators.insert(make_pair(string("three"), &threeCreator)); 

     CreatorMap::iterator creator = creators.find(name); 
     if (creator != creators.end()) { 
      return (*(creator->second))(); 
     } 
     return NULL; 
    } 

    virtual void f() { cout << "NUMBER" << endl; } 
}; 

struct One : Number { 
    virtual void f() { cout << "ONE" << endl; } 
}; 

struct Two : Number { 
    virtual void f() { cout << "TWO" << endl; } 
}; 

struct Three : Number { 
    virtual void f() { cout << "THREE" << endl; } 
}; 

Number* oneCreator() { return new One(); } 
Number* twoCreator() { return new Two(); } 
Number* threeCreator() { return new Three(); } 

int main() { 
    Number *two = Number::getByName(string("two")); 
    two->f(); 
    return 0; 
} 
+0

Спасибо! Одна вещь, на которой я немного не понятна, - это 'typedef Number * (* creatorFP)();'. Что я должен искать, чтобы понять это? – cambunctious

+1

Это typedef, чтобы упростить указатель на определение типа функции. Попробуйте http://stackoverflow.com/questions/4295432/typedef-function-pointer –

+2

@cambunctious: с синтаксисом синтаксиса C++ 11: 'using CreatorF = Number *(); используя creatorFP = CreatorF *; '? – Jarod42

3

Вы можете реализовать свой завод, как следовать

template <typename T> 
static std::pair<std::string, std::function<Number*()>> register_helper() 
{ 
    return { T::name, []() { return new T{}; }}; 
} 

static Number* getByName(const std::string &name) { 
    static const std::map<std::string, std::function<Number*()>> factories = { 
     register_helper<One>(), 
     register_helper<Two>(), 
     register_helper<Three>() 
    }; 

    auto it = factories.find(name); 
    if (it == factories.end()) { 
     return nullptr; 
    } else { 
     return it->second(); 
    } 
} 

Live Demo

+0

Не совсем понимаю, почему вы используете 'std :: function' здесь. Должно быть хорошо с помощью только 'Number * (*)()', no? – dyp

+0

@dyp: 'Номер * (*)()' возможен здесь. Обычно я регистрировался вне/публично, поэтому 'std :: function' позволяет в этом случае захватить лямбда. – Jarod42

+0

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

0

Пытаясь что-то немного проще, чем предыдущая смол и предполагая, что getByName не вызывается с неподдерживаемым значением, что часто.

template <typename TYPE> 
Number * factory() 
{ 
    return new TYPE(); 
} 

static const std::map<std::string, Number*(*)()> factories = 
{ 
    {One::name, factory<One>}, 
    {Two::name, factory<Two>}, 
    {Three::name, factory<Three>} 
}; 

static Number* getByName(const std::string &name) 
{ 
    try 
    { 
     return factories.at(name)(); 
    } 
    catch (std::out_of_range &e) 
    { 
     // Log factory called with bad name as a hint that the programmer 
     // should check calling code. 
     return nullptr; 
    } 
} 

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

static Number* getByName(const std::string &name) 
{ 
    auto factory = factories.find(name); 
    if (factory != factories.end()) 
    { 
     return factory->second(); 
    } 
    return nullptr; 
} 
+0

Почему «намного дешевле» не обрабатывать исключение? – cambunctious

+0

Существует множество вещей, происходящих за кулисами, когда возникает исключение, намного больше, чем сравнение if и iterator. С другой стороны, фактически нет никакой стоимости, если исключение не выбрасывается и нет кода проверки, чтобы содержать ошибки. Если ввод плохого имени действительно является исключительным, используйте исключение. [Второй ответ здесь] (http://stackoverflow.com/questions/13835817/are-exceptions-in-c-really-slow), и это объясняется комментарием. Целая вещь, вероятно, стоит прочитать. – user4581301

Смежные вопросы