C++ 模板与 OOD 问题 - 如何实现类似“模板化类成员变量”的东西?

C++ Templates vs OOD woes - How can I get around implementing something like 'templated class member variables'?

提问人:InquiryOrEnquiry 提问时间:11/6/2023 更新时间:11/7/2023 访问量:102

问:

我试图使用模板节省大量代码重复。 理想情况下,我想按照这个思路做一些事情(我知道它的垃圾代码):

class Registries {
public:

    template<typename T>
    void AddToRegistry(T thing) {
        registry.push_back(thing);
    }

    template<typename T>
    std::vector<T>* GetRegistry<T>() {
        return &registry();
    }

private:
    template<typename T>
    std::vector<T> registry; // auto-magically created vectors for each used type
};


int main() {
    Registries registries;

    CoolThing thing1;
    NiceThing thing2;
    // ... Many N types

    registries.AddToRegistry(thing1);
    registries.AddToRegistry(thing2);

    std::vector<CoolThing>* cool_registry = registries.GetRegistry<CoolThing>();
    std::vector<NiceThing>* nice_registry = registries.GetRegistry<NiceThing>();
}

它当然不起作用,因为我无法对类成员进行模板化 - 不会为每个类型创建一个变量,并且这些函数不知道要返回哪个变量):

我可以这样做,但它们都将存储在静态内存中(不理想):

class CoolThing {
public:
    static std::vector<CoolThing> registry;
};

class NiceThing {
public:
    static std::vector<NiceThing> registry;
};

class Registries {
public:

    template<typename T>
    void AddToRegistry(T thing) {
        T::registry.push_back(thing);
    }
}


int main() {
    Registries registries;

    registries.AddToRegistry(CoolThing());
    registries.AddToRegistry(NiceThing());
    // ... Many N types
}

我还尝试了模板中的 switch 和 if-else,但它们似乎不兼容:

class CoolThing {
public:
    const static ThingType TYPE = COOL;
};

class NiceThing {
public:
    const static ThingType TYPE = NICE;
};

class Registries {
public:

    template<typename T>
    void AddToRegistry(T thing) {

        switch (T::TYPE) {
        
        case ThingType::COOL:
            cool_registry.push_back(thing);
            return;

        case ThingType::NICE:
            nice_registry.push_back(thing);
            return;
        
        default:
            return;
        }
    }

    template<typename T>
    std::vector<T>* GetRegistry<T>() {
        
        if (T::TYPE == ThingType::COOL) {

            return &cool_registry;
        } 
        else if (T::TYPE == ThingType::NICE) {

            return &nice_registry;
        }

        return nullptr;
    }

private:
    std::vector<CoolThing> cool_registry;
    std::vector<NiceThing> nice_registry;
    // ... Many N types
};


int main() {
    Registries registries;

    CoolThing thing1;
    NiceThing thing2;
    // ... Many N types

    registries.AddToRegistry(thing1);
    registries.AddToRegistry(thing2);

    std::vector<CoolThing>* = registries.GetRegistry<CoolThing>();
    std::vector<NiceThing>* = registries.GetRegistry<NiceThing>();
}

我觉得我的选择是静态方式,或者编写一长串(20+ 类型)函数重载以添加到正确的注册表类型中。

任何模板都在那里,知道替代方案吗? 非常感谢

C++ C++17 模板 -元编程

评论

0赞 Pablo 11/6/2023
你有没有想过使用一个包含?如果您的类数量有限,这是一个不错的解决方案。另一种方式(旧的 c++11 之前的方式)应该是多态性,声明一个 Interface,一个抽象基类,用作其他类的遗产。std::vectorstd::variant
0赞 Mark Ransom 11/6/2023
为什么要模板化单个函数而不是整个类?模板类可以很容易地具有模板类型的成员。
0赞 user12002570 11/6/2023
“任何模板都在那里,知道替代方案吗?”是的,我发明了一种使用和做同样事情的方法。请参阅此答案中所示的演示std::anystd::map
0赞 InquiryOrEnquiry 11/6/2023
@Pablo我不想要变体的向量,因为我想将相同的类型紧密地打包在一起,这样我就可以快速迭代它们。我也考虑过多态性,但如果可能的话,我宁愿避免性能下降
0赞 user12002570 11/6/2023
@InquiryOrEnquiry 请参阅内容,避免了多态性和代码重复。

答:

-1赞 Tidemor 11/6/2023 #1

您最有可能寻找的是模板专业化。您可以在模板化定义上为特定类型事例定义行为。

class Regs {
    public:
        template<typename T>
        void AddRegister(T t) { ... } // Generic Case
        
        template<>
        void AddRegister<CoolThing>(CoolThing ct) { ... } // When T is CoolThing

        template<>
        void AddRegister<NiceThing>(NiceThing nt) { ... } // When T is NiceThing
};

或者,由于重载可能会变得非常冗长,因此可以使用映射映射type_info函数指针或类似的东西,这需要在编译时知道所有情况

0赞 Swift - Friday Pie 11/6/2023 #2

如果您修复了不正确的语法并且您对单例(即全局)的想法感到满意,那么第一个版本将正常工作。registries

#include <vector>

struct CoolThing {};
struct NiceThing {};

class Registries {
public:

    template<typename T>
    void AddToRegistry(T thing) {
        registry<T>.push_back(thing);
    }

    template<typename T>
    std::vector<T>* GetRegistry() {
        return &registry<T>;
    }

private:
    template<typename T>  
    static std::vector<T> registry; // template of static member variable
};

template<typename T>
std::vector<T> Registries::registry = {}; 

int main() {
    Registries registries;

    CoolThing thing1;
    NiceThing thing2;
    // ... Many N types

    registries.AddToRegistry(thing1);
    registries.AddToRegistry(thing2);

    std::vector<CoolThing>* cool_registry = registries.GetRegistry<CoolThing>();
    std::vector<NiceThing>* nice_registry = registries.GetRegistry<NiceThing>();
}

否则,您将需要将工厂方法与代理模式混合使用,如果尚不存在,则临时创建此类容器,而不是作为类的成员。Registry

0赞 user12002570 11/6/2023 #3

任何模板都在那里,知道替代方案吗?我觉得我的选择是静态方式,或者编写一长串(20+ 类型)函数重载以添加到正确的注册表类型中

不,当您可以使用 std::anystd::map最大限度地减少代码重复时,您不需要编写如此多的重载,如下所示。

在这里,我们首先创建一个将 Things( 和 在您的示例中)映射到它们各自的注册表 ( 和 )。std::mapCoolThingNiceThingcool_registrynice_registry

class CoolThing{};
class NiceThing{};

class Registries {
public:

    
    template<typename T> void AddToRegistry(T Arg)
    {
        std::any_cast<std::reference_wrapper<std::vector<T>>>(myMap.at(typeid(T))).get().push_back(Arg);
    }

    template<typename T> std::vector<T>* GetRegistry() {
        return &(std::any_cast<std::reference_wrapper<std::vector<T>>>(myMap.at(typeid(T))).get());
    } 

private:
    std::vector<CoolThing> cool_registry;
    std::vector<NiceThing> nice_registry;

    //create std::map 
    std::map<std::type_index, std::any> myMap{{typeid(CoolThing), std::ref(cool_registry)}, 
                                                  {typeid(NiceThing), std::ref(nice_registry)}};
};

int main()
{
    //let's create some instances to push back into the vectors 
    CoolThing C1;
    CoolThing C2;
    
    NiceThing N1;
    NiceThing N2;
    NiceThing N3;

    Registries registries;
    
    //lets add the instances using the member function template
    registries.AddToRegistry(C1); 
    //lets confirm if the entity was added correctly into the vector 
    std::cout << "Size of cool_registry: " << (*registries.GetRegistry<CoolThing>()).size(); //prints 1 as expected
    std::cout << "\nSize of nice_registry: " << (*registries.GetRegistry<NiceThing>()).size(); //prints 0 as expected

    //lets add some more instances
    registries.AddToRegistry(C2);
    registries.AddToRegistry(N1);
    registries.AddToRegistry(N2);
    registries.AddToRegistry(N3); 

    //lets confirm if the entity was added correctly into the vector 
    std::cout << "\nSize of cool_registry: " << (*registries.GetRegistry<CoolThing>()).size(); //prints 2 as expected
    std::cout << "\nSize of nice_registry: " << (*registries.GetRegistry<NiceThing>()).size(); //prints 3 as expected
    
}

工作演示

评论

1赞 InquiryOrEnquiry 11/6/2023
太棒了!我想这可能就是这样,谢谢!我不熟悉这些类型。它有点像 switch/if-else 版本,只是这似乎可以编译。
0赞 user12002570 11/6/2023
@InquiryOrEnquiry 不客气。
0赞 joergbrech 11/7/2023 #4

另一个不依赖于运行时类型擦除 () 或全局单例的解决方案是存储以下元组:std::anystd::vector<T>

#include <vector>

template <typename... Ts>
class Registries {
public:

    template<typename T>
    void AddToRegistry(T thing) {
        GetRegistry<T>().push_back(thing);
    }

    template<typename T>
    std::vector<T>& GetRegistry() {
        return std::get<std::vector<T>>(registry);
    }

private:
    std::tuple<std::vector<Ts>...> registry;
};

struct CoolThing {};
struct NiceThing {};


int main() {
    Registries<CoolThing, NiceThing> registries;

    CoolThing thing1;
    NiceThing thing2;
    // ... Many N types

    registries.AddToRegistry(thing1);
    registries.AddToRegistry(thing2);

    std::vector<CoolThing>& cool_registry = registries.GetRegistry<CoolThing>();
    std::vector<NiceThing>& nice_registry = registries.GetRegistry<NiceThing>();
}

请注意,这要求您在实例化注册表时了解所有可能的类型。

工作演示

PS:如果这困扰您,这里有一个没有此限制的类型擦除解决方案: https://godbolt.org/z/nvWchfMaK

评论

0赞 InquiryOrEnquiry 11/7/2023
另一个优雅的解决方案!我想我会使用它 - 它很简单,库更少,开销最小。我在编译时知道所有类型,所以这很完美,谢谢!