#c #templates
#c #шаблоны
Вопрос:
Есть ли какой-либо способ реализовать функциональность приведенного ниже кода без создания сопоставления между строками и классами вручную?
templatelt;class base, typename Tgt; base* f(const std::string amp;type, T amp;c) { if(type == "ClassA") return new ClassA(c); else if(type == "ClassB") return new ClassB(c); // many more else if... return nullptr; }
Все занятия выглядят примерно так:
class ClassA: public BaseClass { public: std::string label="ClassA"; ... };
И мы можем использовать его как:
BaseClass *b = flt;BaseClassgt;("ClassA", DifferentObject);
Каждый новый класс приводит к новой if else
строке кода. Есть ли какой-либо способ автоматизировать эту f
функцию, чтобы она сама «обновлялась» при добавлении нового поддерживаемого класса? Решение должно работать для C 11.
Комментарии:
1. Короткий ответ: Нет. Вы можете использовать макросы для автоматизации отдельных его частей. Кроме того, вы можете использовать перечисление вместо строки и использовать переключатель, чтобы сделать код немного аккуратнее.
2. Известна ли строка только во время выполнения? В противном случае,
flt;ClassAgt;
кажется, лучше.3. Если имя класса и строка равны, то вы можете создать макрос, который выполняет сопоставление. Он заменяет только структуру if…else, но нуждается в перекомпиляции, если добавляется новый класс. Является ли это приемлемым решением?
4. Да, строка известна во время выполнения. @n314159 не могли бы вы опубликовать пример макроса. Спасибо.
5. @zerocukor287 Да, это звучит хорошо для меня, но я не знаю, как написать такой макрос
Ответ №1:
Возможный макрос:
#include lt;memorygt; #include lt;stringgt; class BaseClass {}; class ClassA : public BaseClass { public: std::string label = "ClassA"; explicit ClassA(int /*unused*/) {} }; class ClassB : public BaseClass { public: std::string label = "ClassB"; explicit ClassB(int /*unused*/) {} }; templatelt;class base, typename Tgt; auto f(const std::string amp;type, T c) -gt; std::unique_ptrlt;basegt; { #define CASE(NAME) if (type == "NAME") { return std::unique_ptrlt;basegt;(new NAME(c)); } CASE(ClassA) CASE(ClassB) //... #undef CASE return nullptr; // Statement at the end needed for last else! } auto main() -gt; int { auto b = flt;BaseClassgt;("ClassA", 0); }
Также используйте unique_ptr
, так как управление необработанными указателями в памяти-это ЗЛО.
Комментарии:
1. Спасибо за это. Это все еще требует изменения функции f при введении нового класса. Есть ли какой-нибудь способ автоматизировать его дальше? Как-то найти все производные классы базового класса?
2. Нет, это явно невозможно, поскольку новые производные классы могут быть определены где угодно, даже в совершенно несвязанных единицах перевода, которые не могли бы изменить f.
Ответ №2:
В том случае, если имя класса равно строке, вы можете упростить свой код с помощью следующего макроса:
#define STRING_TO_CLASS (className) if(type == "className") return new className(c); templatelt;class base, typename Tgt; base* f(const std::string amp;type, T amp;c) { STRING_TO_CLASS(ClassA) STRING_TO_CLASS(ClassB) return nullptr; }
Лично я ненавижу макросы, но это беспокоит только меня. Однако во время компиляции после разрешения макросов будет сгенерирован следующий код.
templatelt;class base, typename Tgt; base* f(const std::string amp;type, T amp;c) { if(type == "ClassA") return new ClassA(c); if(type == "ClassB") return new ClassB(c); return nullptr; }
Как вы видите, в итоге удаляется только else
ключевое слово. Кроме того, вам необходимо изменить свой код, если добавляется новый класс.
Комментарии:
1. Спасибо за это. Этот макрос немного облегчает жизнь, но все же «регистрацию» классов я бы описал как ручную.
2. Вам не нужна «автоматическая» регистрация. Кто-нибудь может пройти
std::ofstream
мимо и начать перезаписывать файлы!
Ответ №3:
Вы можете использовать шаблон реестра следующим образом:
#include lt;mapgt; #include lt;functionalgt; #include lt;stringgt; templatelt; typename T, typename X gt; using Factory = std::functionlt; T* ( Xamp; ) gt;; templatelt; typename Base, typename X gt; struct Registry { using Map = std::maplt;std::string,Factorylt;Base,Xgt; gt;; static Map registry; templatelt; typename T gt; struct Register { Register( const std::stringamp; name ) { registry[ name ] = []( Xamp; x ) -gt; T* { return new T(x); }; } }; }; templatelt; typename Base, typename X gt; Base* factory(const std::string amp;type, X amp;c ) { auto it = Registrylt;Base,Xgt;::registry.find( type ); if ( it!=Registrylt;Base,Xgt;::registry.end() ) { return (it-gt;second)(c); } return nullptr; } struct X {}; struct A { A( Xamp; x ) {}; virtual ~A() {} }; struct B : public A { B( Xamp; x ) : A(x) {}; }; struct C : public A { C( Xamp; x ) : A(x) {}; }; struct D : public B { D( Xamp; x ) : B(x) {}; }; // Register class templatelt;gt; Registrylt;A,Xgt;::Map Registrylt;A,Xgt;::registry{}; Registrylt;A,Xgt;::Registerlt;Bgt; regB( "B" ); Registrylt;A,Xgt;::Registerlt;Cgt; regC( "C" ); Registrylt;A,Xgt;::Registerlt;Dgt; regD( "D" ); #include lt;iostreamgt; int main() { X x; A* ptr = factorylt;A,Xgt;( "B", x ); B* bptr = dynamic_castlt;B*gt;( ptr ); if ( bptr!= nullptr ) { std::cout lt;lt; "Success!" lt;lt; std::endl; return 0; } std::cout lt;lt; "Failed!" lt;lt; std::endl; return 1; }
Ответ №4:
Правильный шаблон для использования здесь-это шаблон «Абстрактная фабрика».
Может быть, вы сможете это посмотреть.
Чтобы дать вам представление (и не более), что возможно, я покажу вам приведенный ниже код, который даже принимает конструкторы с другой подписью.
#include lt;iostreamgt; #include lt;mapgt; #include lt;utilitygt; #include lt;anygt; // Some demo classes ---------------------------------------------------------------------------------- struct Base { Base(int d) : data(d) {}; virtual ~Base() { std::cout lt;lt; "Destructor Basen"; } virtual void print() { std::cout lt;lt; "Print Basen"; } int data{}; }; struct Child1 : public Base { Child1(int d, std::string s) : Base(d) { std::cout lt;lt; "Constructor Child1 " lt;lt; d lt;lt; " " lt;lt; s lt;lt; "n"; } virtual ~Child1() { std::cout lt;lt; "Destructor Child1n"; } virtual void print() { std::cout lt;lt; "Print Child1: " lt;lt; data lt;lt; "n"; } }; struct Child2 : public Base { Child2(int d, char c, long l) : Base(d) { std::cout lt;lt; "Constructor Child2 " lt;lt; d lt;lt; " " lt;lt; c lt;lt; " " lt;lt; l lt;lt; "n"; } virtual ~Child2() { std::cout lt;lt; "Destructor Child2n"; } virtual void print() { std::cout lt;lt; "Print Child2: " lt;lt; data lt;lt; "n"; } }; struct Child3 : public Base { Child3(int d, long l, char c, std::string s) : Base(d) { std::cout lt;lt; "Constructor Child3 " lt;lt; d lt;lt; " " lt;lt; l lt;lt; " " lt;lt; c lt;lt; " " lt;lt; s lt;lt; "n"; } virtual ~Child3() { std::cout lt;lt; "Destructor Child3n"; } virtual void print() { std::cout lt;lt; "Print Child3: " lt;lt; data lt;lt; "n"; } }; using UPTRB = std::unique_ptrlt;Basegt;; template lt;class Child, typename ...Argsgt; UPTRB createClass(Args...args) { return std::make_uniquelt;Childgt;(args...); } // The Factory ---------------------------------------------------------------------------------------- template lt;class Key, class Objectgt; class Factory { std::maplt;Key, std::anygt; selector; public: Factory() : selector() {} Factory(std::initializer_listlt;std::pairlt;const Key, std::anygt;gt; il) : selector(il) {} templatelt;typename Functiongt; void add(Key key, Functionamp;amp; someFunction) { selector[key] = std::any(someFunction); }; template lt;typename ... Argsgt; Object create(Key key, Args ... args) { if (selector.find(key) != selector.end()) { return std::any_castlt;std::add_pointer_tlt;Object(Args ...)gt;gt;(selector[key])(args...); } else return nullptr; } }; int main() { // Define the factory with an initializer list Factorylt;int, UPTRBgt; factory{ {1, createClasslt;Child1, int, std::stringgt;}, {2, createClasslt;Child2, int, char, longgt;} }; // Add a new entry for the factory factory.add(3, createClasslt;Child3, int, long, char, std::stringgt;); // Some test values std::string s1(" Hello1 "); std::string s3(" Hello3 "); int i = 1; const int ci = 1; intamp; ri = i; const intamp; cri = i; intamp;amp; rri = 1; UPTRB b1 = factory.create(1, 1, s1); UPTRB b2 = factory.create(2, 2, '2', 2L); UPTRB b3 = factory.create(3, 3, 3L, '3', s3); b1-gt;print(); b2-gt;print(); b3-gt;print(); b1 = factory.create(2, 4, '4', 4L); b1-gt;print(); return 0; }