用什么代替c++中的模板化虚方法

What to use instead of templated virtual method in C++

本文关键字:方法 什么 c++      更新时间:2023-10-16

我有这样一个例子:

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虚拟和非模板化,并传递一个可以转换为任何类型的类型。可能是stringboost::anyboost::variant

如果你不能使用boost::anyboost::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 &param, 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);
}

但是要访问每个entityset_value函数,您需要查看与每个变量相关的tag变量:

template<typename T>
void set_value_of_text(entity *e, const std::string &param, 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以外的其他类型,以便将来更容易添加实体类型

相关文章: