提问人:InquiryOrEnquiry 提问时间:11/6/2023 更新时间:11/7/2023 访问量:102
C++ 模板与 OOD 问题 - 如何实现类似“模板化类成员变量”的东西?
C++ Templates vs OOD woes - How can I get around implementing something like 'templated class member variables'?
问:
我试图使用模板节省大量代码重复。 理想情况下,我想按照这个思路做一些事情(我知道它的垃圾代码):
class Registries {
public:
template<typename T>
void AddToRegistry(T thing) {
registry.push_back(thing);
}
template<typename T>
std::vector<T>* GetRegistry<T>() {
return ®istry();
}
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+ 类型)函数重载以添加到正确的注册表类型中。
任何模板都在那里,知道替代方案吗? 非常感谢
答:
您最有可能寻找的是模板专业化。您可以在模板化定义上为特定类型事例定义行为。
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函数指针或类似的东西,这需要在编译时知道所有情况
如果您修复了不正确的语法并且您对单例(即全局)的想法感到满意,那么第一个版本将正常工作。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 ®istry<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
任何模板都在那里,知道替代方案吗?我觉得我的选择是静态方式,或者编写一长串(20+ 类型)函数重载以添加到正确的注册表类型中
不,当您可以使用 std::any
和 std::map
并最大限度地减少代码重复时,您不需要编写如此多的重载,如下所示。
在这里,我们首先创建一个将 Things( 和 在您的示例中)映射到它们各自的注册表 ( 和 )。std::map
CoolThing
NiceThing
cool_registry
nice_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
}
评论
另一个不依赖于运行时类型擦除 () 或全局单例的解决方案是存储以下元组:std::any
std::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
评论
std::vector
std::variant
std::any
std::map