如果我们有一个base *类,如何访问派生的Template类的成员函数

时间:2017-07-29 15:23:30

标签: c++ c++11 templates

我有一个从基类派生的模板类。

同样在main中,我有一个vector容器,其中包含各种类型的派生类。在不知道Derived类的类型的情况下,我该怎么做 访问main中的成员函数?

模板专业化可以很好地解决这个问题。但是,我该怎么做?

class Base{ // The base class of a template class
public:
    Base(){}
    virtual ~Base(){}
};

template <typename T>
class Derived : public Base{ // The template class,
                             // derived from the base class.
    T data;
public:
     Derived(){}
    ~Derived(){}

       T  get()       { return data; }
     void set( T val) { data = val;  }
};

int main() {

    vector <Base*> myObject;
    // I have different types of Derived class
    // in a vector conteiner.

    Derived<int>  *D1=new Derived<int>;
    Derived<float>*D2=new Derived<float>;
    Derived<char> *D3=new Derived<char>;

    myObject.push_back(D1);
    myObject.push_back(D2);
    myObject.push_back(D3);

    for (int i=0;i<myObject.size();i++){
         // myObject[i]->set(4);  Error! : myObject is a vector of <Base*>
         // myObject[i]->get();   Error! : not a vector of <Derived*>
    }

  return 0;
}

3 个答案:

答案 0 :(得分:4)

您将无法使用非模板基类执行所需操作。您要做的是通过存储指向非模板基类的指针来擦除模板派生类的类型。这称为类型擦除 - https://en.wikipedia.org/wiki/Type_erasure

您可以dynamic_cast然后打开派生类的类型。例如

for (int i=0;i<myObject.size();i++){

     if (auto* ptr = dynamic_cast<Derived<int>*>(myObject[i])) {
         ptr->set(4);
         ptr->get();
     } else if (auto* ptr = dynamic_cast<Derived<double>*>(myObject[i])) {
         ptr->set(1.0);
         ptr->get();
     }
}

std::any如何解决此界面问题可能会引起您的兴趣。看看http://en.cppreference.com/w/cpp/utility/any/any_cast

另一种方法是不投资类型擦除并使用std::variant

答案 1 :(得分:1)

标准的c ++方法无法实现您想要的确切行为。此设计不是多态的,并且您的对象中没有任何类型信息。此外,您似乎没有(在c ++上下文中)对象的公共接口,因为所有函数都在不同的类型上运行。

这似乎是Variant类型的一个实现,你想在一个容器中存储不同的值(int,string,float等)。

解决此问题的最简单方法是向基类添加type字段,并为派生类中的每种类型分配特定值。但是,每次要访问实际值时,都应手动检查并投射到所需的实际时间。

enum ValueType
{
    Bool, Int, Float
};

class Base
{
protected:
    ValueType _type;

public:
    ValueType type() const { return _type; }
};

// usage example
Base *val = vector[something];
switch (val->type()) {
    case Int: { int intvalue = Derived<int>(val)->get(); }
}

另一种方法是为get / set创建公共接口,让它们返回/接受一些可以表示任何类型值的公共类型,比如字符串,并像往常一样使用继承和虚函数没有模板。这种实现的缺点是字符串处理和存储的成本。

class Base
{
public:
    virtual string get() const = 0;
    virtual void set(const string &newval) = 0;
};

class IntValue : public Base
{
public:
    /* imagine we have that magic IntToStr function */
    string get() const override { return IntToStr(_value); }

private:
    int _value;
};

我认为最有效的方法是在基类中包含所有可能的getter和setter,并将它们覆盖到特定的派生类中。通过这种方式,您可以获得统一的界面,并可以进行额外的便利值转换(例如将int读取为char或bool或string)。

class Base
{
public:
    virtual bool getBool() const = 0;
    virtual int getInt() const = 0;
    // etc for other types getters and setters
};

class IntValue
{
public:
    bool getBool() const override { return _value != 0; }
    int getInt() const override { return _value; }
    // for incompatible types - throw error or return default value
};

还有其他方法尽可能快地实现这一点,但也尽可能丑陋的是模板化的get / set版本及其实现(没有虚拟)专门针对每种类型和每种可能的变体类型。有了这个,您必须保留内部type变量,该变量将保存实际值类型。在这种情况下,你根本不需要继承。

答案 2 :(得分:0)

问题在于您实施类型的方式。当您声明sed 's/\(pseudoCityCode="\)[^"]*/\1abcd/'<<<'<ns1:ReservationPCC pseudoCityCode="1234" pseudoCityType="Booking" supplierService="Sabre"/>' 时,C ++准备存储能够具有std::vector<Base*>功能而不再是的对象。当您将三个对象放入向量时,C ++会忘记它们的基础类型。因此,当您致电Base时,系统无法知道要调用哪个myObject[i]->set(4)

通常,解决方案是在基类中创建虚方法。但是,正如您所提到的,您不能这样做,因为您在setget中有模板类型。下一个想法是实现模板化的基类并具有set子类Derived<T>。这样可以解决问题,但会创建一个新问题:你不能拥有异构对象的向量。

所以我认为从根本上说,你需要考虑你要做的事情。以下是一些问题要问自己。

  1. 您是否知道预先存储了多少个对象和哪些对象?如果您在编译时知道对象的数量和类型,请考虑Base<T>std::tuple个对象。

  2. 由于您将所有对象都设置为整数Derived,您是否知道所有对象类型都可以转换为整数/从整数转换?如果是这样,您可以在基类中编写4get_int作为虚函数,并调用它们,这些函数可以委托给子类中的set_intget

  3. 如果您不知道前面需要多少个对象,但是您知道它们都来自一组有限的对象,请考虑set std::vector({1}}或std::variant,如果您无权访问C ++ 17。

  4. 如果你真的需要所有的一般性,你可以使用boost::variant(或者,如果你没有C ++ 17,再次,std::any)绕过类型系统。但真的考虑你是否可以做得更好。上述解决方案将为您提供更好的类型安全性,并使您的代码不易出错。