用什么代替c++中的模板化虚方法
What to use instead of templated virtual method in C++
我有这样一个例子:
class Entity
{
float mX;
float mY;
string mName;
// other attributes
public:
void setPosX(float x);
void setPosY(float y);
void setName(string name);
// other setters
template<typename T>
virtual void setAttributeValue(string param, T value)
{
if(param == "x")
setX(value);
else if(param == "y")
setY(value);
else if(param == "name")
setName(value);
// ...
}
};
class SpecialEntity : public Entity
{
int specialAttr;
// other special attributes
public:
void setSpecialAttr(int val);
// other setters
template<typename T>
virtual void setAttributeValue(string param, T value)
{
Entity::setAttributeValue(param, value);
if(param == "specialAttr")
setSpecialAttr(value);
// ...
}
};
这将无法编译,因为模板化的虚方法是不允许的。
我需要这个在我的编辑器应用程序,它有一个属性网格控件,根据该控件中的属性的名称,我需要从实体类或实体的继承类调用一个方法来设置属性值。
实现这一目标的最佳方法是什么
当我必须这样做时,我使用的选项之一是传递boost::any
而不是T
。
virtual void setAttributeValue(string param, boost::any value)
{
if(param == "x")
setX(boost::any_cast<int>(value));
else if(param == "y")
setY(boost::any_cast<int>(value));
else if(param == "name")
setName(boost::any_cast<std::string>(value));
// ...
}
按老办法做
setX(int)
setY(float)
setZ(string)
更安全(编译器会发现错误)和更快
你不能创建这样的模板函数,无论是虚拟的还是其他的。每个版本的模板函数中的所有函数调用都必须编译,即使你从未打算调用它们。
模板函数所发生的一切都是函数的副本被盖掉,T
被所需的每个类型替换。如果手工执行,很容易看到它无法编译。在这种情况下,没有setX
占用string
,也没有setName
占用float
:
class Entity
{
float mX;
std::string mName;
public:
void setX(float x){ mX = x; }
void setName(std::string name){ mName = name; }
void setAttributeValue(std::string param, float value)
{
if(param == "x")
setX(value);
else if(param == "name")
setName(value); // Error!
}
void setAttributeValue(std::string param, std::string value)
{
if(param == "x")
setX(value); // Error!
else if(param == "name")
setName(value);
}
};
我建议您使setAttributeValue
虚拟和非模板化,并传递一个可以转换为任何类型的类型。可能是string
、boost::any
或boost::variant
。
如果你不能使用boost::any
或boost::variant
,你可以创建自己的Value
接口传入:
struct Value {
virtual float getFloat() const = 0;
virtual std::string getString() const = 0;
virtual int getInt() const = 0;
};
struct ValueBase : Value {
float getFloat() const override { throw std::runtime_error("Not float"); }
std::string getString() const override { throw std::runtime_error("Not string"); }
int getInt() const override { throw std::runtime_error("Not int"); }
};
struct FloatValue : ValueBase {
float value;
FloatValue(float value) : value(value){}
float getFloat() const override { return value; }
};
struct StringValue : ValueBase {
std::string value;
StringValue(std::string value) : value(value){}
std::string getString() const override { return value; }
};
struct IntValue : ValueBase {
int value;
IntValue(int value) : value(value){}
int getInt() const override { return value; }
};
class Entity {
float mX;
float mY;
std::string mName;
public:
void setX(float x);
void setY(float y);
void setName(std::string name);
virtual void setAttributeValue(std::string param, const Value& value) {
if(param == "x")
setX(value.getFloat());
else if(param == "y")
setY(value.getFloat());
else if(param == "name")
setName(value.getString());
}
};
class SpecialEntity : public Entity {
int specialAttr;
public:
void setSpecialAttr(int val);
void setAttributeValue(std::string param, const Value& value) override {
Entity::setAttributeValue(param, value);
if(param == "specialAttr")
setSpecialAttr(value.getInt());
}
};
现场演示。
您可以使用奇怪的反复出现的模板模式、多态基类、一些标记和多态值类来实现您想要的功能
首先我们需要一个多态值类型:
struct value{ virtual ~value(){} };
struct text_value: public value{ text_value(const std::string &str_): str(str_){} std::string str; };
struct int_value: public value{ int_value(int i_): i(i_){} int i; };
auto make_value(const std::string &str){ return text_value{str}; }
auto make_value(int i){ return int_value{i}; }
然后我们的静态(CRTP)和动态多态性:
enum class entity_tag{
text
};
class entity{
public:
entity(entity_tag tag_): tag(tag_){}
virtual ~entity(){}
entity_tag tag;
};
template<typename Entity>
class entity_base: entity{
public:
entity_base(): entity(this->get_tag()){}
template<typename T>
void set_value(const std::string ¶m, T &&val){
reinterpret_cast<Entity*>(this)->set_value_impl(param, std::forward<T>(val));
}
static entity_tag get_tag(){
return Entity::get_tag_impl();
}
};
然后我们可以开始定义一些类来实现我们的接口!(set_value_impl
and get_tag_impl
)
class text_entity: public entity_base<text_entity>{
protected:
std::string font = "times new roman";
int font_size = 10;
std::string text = "";
void set_text(value &&v){
auto tv = dynamic_cast<text_value&&>(v);
text = std::move(tv.str);
}
void set_font(value &&v){
auto tv = dynamic_cast<text_value&&>(v);
font = std::move(tv.str);
}
void set_font_size(value &&v){
auto iv = dynamic_cast<int_value&&>(v);
font_size = iv.i;
}
public:
static entity_tag get_tag_impl(){ return entity_tag::text; }
template<typename T>
void set_value_impl(const std::string &str, T &&val){
auto v = make_value(val);
if(str == "text")
set_text(std::move(v));
else if(str == "font")
set_font(std::move(v));
else if(str == "font_size")
set_font_size(std::move(v));
else
throw "invalid parameter";
}
};
就像1、2、3一样简单!
你可以像在你的问题中那样使用它:
int main(){
text_entity text;
text.set_value("font", "comic sans");
text.set_value("font_size", 24);
text.set_value("text", "Comic sans sucks");
}
它也不会让你尝试分配例如float
给"font"
,因为没有float
的值类型。如果有,它会在set_font
内抛出异常。
你可以在每个entity
子类中定义value
子类,这样如果给定的值对任何给定的参数都不可能,编译器将总是报错。
也可以将实体存储在其他实体类型的容器中:
int main(){
std::vector<entity*> entities;
text_entity text0;
text_entity text1;
pic_entity pic0;
slider_entity slider0;
entities.push_back(&text0);
entities.push_back(&text1);
entities.push_back(&pic0);
entities.push_back(&slider0);
}
但是要访问每个entity
的set_value
函数,您需要查看与每个变量相关的tag
变量:
template<typename T>
void set_value_of_text(entity *e, const std::string ¶m, T &&t){
if(e->tag != entity_tag::text)
throw "entity is not a text entity";
dynamic_cast<text_entity*>(e)->set_value(param, std::forward<T>(t));
}
您可以为tag
类型使用enum class
以外的其他类型,以便将来更容易添加实体类型
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 在 c++ 中拥有一组结构的正确方法是什么?
- 通过JNI传递数据数组的最快方法是什么
- 有什么好的方法可以让系统调用代理允许在单元测试中进行模拟
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 当无法使用模板和宏时,生成类型变体C++代码的最简单方法是什么?
- 在另一个类视图中添加最多2个图表的正确方法是什么
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 在C++中包含原型文件的正确方法是什么?
- 在 OpenCV C++ 中估计基本矩阵之前对相应点进行归一化的正确方法是什么?
- 在PostgreSQL中根据它们的ID选择大量行的最快方法是什么?
- 在OSX上使用CMake将Adobe的XMP工具包构建为共享库的最简单方法是什么?