简体   繁体   English

在运行时选择 C++ 模板

[英]Choose C++ template at runtime

Is there any way to achieve the functionality of below code without creating the mapping between strings and classes manually?有没有办法在不手动创建字符串和类之间的映射的情况下实现以下代码的功能?

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;
}

All classes looks something like this:所有类看起来像这样:

class ClassA: public BaseClass {
public:
    std::string label="ClassA";
    ...

};

And we can use it as:我们可以将其用作:

BaseClass *b = f<BaseClass>("ClassA", DifferentObject);

Each new class results in a new if else line of code.每个新的 class 都会产生一个新的if else代码行。 Is there any way to automate this so f function "updates" itself when new supported class is added?有什么方法可以自动执行此操作,以便在添加新支持的f时 function “更新”自身? The solution must work for C++11.该解决方案必须适用于 C++11。

A possible macro:一个可能的宏:

#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);
}

Also use unique_ptr since memory managing raw pointers are EVIL.也使用unique_ptr因为 memory 管理原始指针是邪恶的。

In that case if the class name is equal to the string, you can simplify your code with the following macro:在这种情况下,如果 class 名称等于字符串,则可以使用以下宏简化代码:

#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;
}

Personally I hate macros, but it disturbs only me.我个人讨厌宏,但它只打扰我。 However, at compile time, the following code wil be generated, after the macros are resolved.但是,在编译时,将在解析宏后生成以下代码。

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;
}

As you see, in the end only the else keyword is removed.如您所见,最后只删除了else关键字。 Also, you need to modify your code if a new class is added.此外,如果添加了新的 class,则需要修改代码。

You could use the registry pattern like this:您可以像这样使用注册表模式:

#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;
}

To correct pattern to use here is the "Abstract Factory" pattern.更正此处使用的模式是“抽象工厂”模式。

Maybe you can look it up.也许你可以查一下。

To give you and idea (and not more), what is possible, I will show you the below code, which even accepts constructors with different signature.为了给你和想法(而不是更多),什么是可能的,我将向你展示下面的代码,它甚至接受具有不同签名的构造函数。

#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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM