【问题标题】:Choose C++ template at runtime在运行时选择 C++ 模板
【发布时间】:2022-01-11 05:53:21
【问题描述】:

有什么方法可以在不手动创建字符串和类之间的映射的情况下实现以下代码的功能?

template<class base, typename T>
base* f(const std::string &type, T &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 = f<BaseClass>("ClassA", DifferentObject);

每个新类都会产生一个新的if else 代码行。有什么方法可以自动执行此操作,以便 f 函数在添加新的支持类时自行“更新”?该解决方案必须适用于 C++11。

【问题讨论】:

  • 简短回答:不。您可以使用宏来自动化其中的一部分。此外,您可以使用枚举而不是字符串,并使用开关使代码更整洁。
  • 字符串是否只在运行时知道?否则,f&lt;ClassA&gt; 似乎更好。
  • 如果类名和字符串相等,那么您可以创建一个宏来进行映射。它只是替换 if...else 结构,但如果添加了新类,则需要重新编译。这是可以接受的解决方案吗?
  • 是的,字符串在运行时是已知的。 @n314159 能否请您发布一个示例宏。谢谢。
  • @zerocukor287 是的,这听起来不错,但我不知道如何编写这样的宏

标签: c++ templates


【解决方案1】:

一个可能的宏:

#include <memory>
#include <string>

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*/) {}
};
template<class base, typename T>
auto f(const std::string &type, T c) -> std::unique_ptr<base> {
#define CASE(NAME)                                                                                                     \
    if (type == "NAME") {                                                                                              \
        return std::unique_ptr<base>(new NAME(c));                                                                     \
    }
    CASE(ClassA)
    CASE(ClassB)
//...
#undef CASE
    return nullptr;  // Statement at the end needed for last else!
}
auto main() -> int {
    auto b = f<BaseClass>("ClassA", 0);
}

也使用unique_ptr,因为内存管理原始指针是邪恶的。

【讨论】:

  • 谢谢。引入新类时仍然需要更改 f 函数。有没有办法进一步自动化?以某种方式找到基类的所有派生类?
  • 不,这显然是不可能的,因为可以在任何地方定义新的派生类,即使是在完全不相关的翻译单元中也无法更改 f。
【解决方案2】:

在这种情况下,如果类名等于字符串,您可以使用以下宏来简化代码:

#define STRING_TO_CLASS (className) if(type == "className") return new className(c);

template<class base, typename T>
base* f(const std::string &type, T &c) {

    STRING_TO_CLASS(ClassA)
    STRING_TO_CLASS(ClassB)

    return nullptr;
}

我个人讨厌宏,但它只会让我感到不安。但是,在编译时,会在解析宏后生成以下代码。

template<class base, typename T>
base* f(const std::string &type, T &c) {

    if(type == "ClassA") return new ClassA(c);
    if(type == "ClassB") return new ClassB(c);

    return nullptr;
}

如您所见,最后只删除了 else 关键字。此外,如果添加了新类,您需要修改代码。

【讨论】:

  • 谢谢。这个宏使生活更轻松,但仍然可以“注册”我将描述为手动的类。
  • 您不想“自动”注册。有人可以通过std::ofstream 并开始覆盖文件!
【解决方案3】:

您可以像这样使用注册表模式:

#include <map>
#include <functional>
#include <string>

template< typename T, typename X >
using Factory = std::function< T* ( X& ) >;


template< typename Base, typename X >
struct Registry {

    using Map = std::map<std::string,Factory<Base,X> >;
    static Map registry;

    template< typename T >
    struct Register {
        Register( const std::string& name ) {
            registry[ name ] = []( X& x ) -> T* { return new T(x); };
        }
    };
};

template< typename Base, typename X >
Base* factory(const std::string &type, X &c ) {
    auto it = Registry<Base,X>::registry.find( type );
    if ( it!=Registry<Base,X>::registry.end() ) {
        return (it->second)(c);
    }
    return nullptr;
}

struct X {};

struct A {
    A( X& x ) {};
    virtual ~A() {}
};

struct B : public A {
    B( X& x ) : A(x) {};
};

struct C : public A {
    C( X& x ) : A(x) {};
};

struct D : public B {
    D( X& x ) : B(x) {};
};



// Register class
template<> Registry<A,X>::Map Registry<A,X>::registry{};
Registry<A,X>::Register<B> regB( "B" );
Registry<A,X>::Register<C> regC( "C" );
Registry<A,X>::Register<D> regD( "D" );

#include <iostream>
int main() {
    X x;
    A* ptr = factory<A,X>( "B", x );
    B* bptr = dynamic_cast<B*>( ptr );
    if ( bptr!= nullptr ) {
        std::cout << "Success!" << std::endl;
        return 0;
    }
    std::cout << "Failed!" << std::endl;
    return 1;
}

【讨论】:

    【解决方案4】:

    此处使用的正确模式是“抽象工厂”模式。

    也许你可以查一下。

    为了给你和想法(而不是更多),什么是可能的,我将向你展示下面的代码,它甚至接受具有不同签名的构造函数。

    #include <iostream>
    #include <map>
    #include <utility>
    #include <any>
    
    
    // Some demo classes ----------------------------------------------------------------------------------
    struct Base {
        Base(int d) : data(d) {};
        virtual ~Base() { std::cout << "Destructor Base\n"; }
        virtual void print() { std::cout << "Print Base\n"; }
        int data{};
    };
    struct Child1 : public Base {
        Child1(int d, std::string s) : Base(d) { std::cout << "Constructor Child1 " << d << " " << s << "\n"; }
        virtual ~Child1() { std::cout << "Destructor Child1\n"; }
        virtual void print() { std::cout << "Print Child1: " << data << "\n"; }
    };
    struct Child2 : public Base {
        Child2(int d, char c, long l) : Base(d) { std::cout << "Constructor Child2 " << d << " " << c << " " << l << "\n"; }
        virtual ~Child2() { std::cout << "Destructor Child2\n"; }
        virtual void print() { std::cout << "Print Child2: " << data << "\n"; }
    };
    struct Child3 : public Base {
        Child3(int d, long l, char c, std::string s) : Base(d) { std::cout << "Constructor Child3 " << d << " " << l << " " << c << " " << s << "\n"; }
        virtual ~Child3() { std::cout << "Destructor Child3\n"; }
        virtual void print() { std::cout << "Print Child3: " << data << "\n"; }
    };
    
    
    
    using UPTRB = std::unique_ptr<Base>;
    
    
    template <class Child, typename ...Args>
    UPTRB createClass(Args...args) { return std::make_unique<Child>(args...); }
    
    // The Factory ----------------------------------------------------------------------------------------
    template <class Key, class Object>
    class Factory
    {
        std::map<Key, std::any> selector;
    public:
        Factory() : selector() {}
        Factory(std::initializer_list<std::pair<const Key, std::any>> il) : selector(il) {}
    
        template<typename Function>
        void add(Key key, Function&& someFunction) { selector[key] = std::any(someFunction); };
    
        template <typename ... Args>
        Object create(Key key, Args ... args) {
            if (selector.find(key) != selector.end()) {
                return std::any_cast<std::add_pointer_t<Object(Args ...)>>(selector[key])(args...);
            }
            else return nullptr;
        }
    };
    
    int main()
    {
        // Define the factory with an initializer list
        Factory<int, UPTRB> factory{
            {1, createClass<Child1, int, std::string>},
            {2, createClass<Child2, int, char, long>}
        };
    
        // Add a new entry for the factory
        factory.add(3, createClass<Child3, int, long, char, std::string>);
    
    
        // Some test values
        std::string s1(" Hello1 "); std::string s3(" Hello3 ");
        int i = 1;  const int ci = 1;   int& ri = i;    const int& cri = i;   int&& 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->print();
        b2->print();
        b3->print();
        b1 = factory.create(2, 4, '4', 4L);
        b1->print();
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-11
      • 2019-12-12
      • 2016-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-12
      • 2018-06-22
      相关资源
      最近更新 更多