指向派生对象的指针的自动向下转换

4

早上好,

我有一个模板化类,想要通过指向指针的向量来操作对象。要使用指向模板化类的指针向量,我需要让该类派生自非模板化类,并已经这样做了。

我的问题在于:从基类指针调用派生类的方法时,我不能使用虚函数,因为模板函数不能被声明为虚函数。我需要进行显式转换,这很繁琐:一旦我使用 new 创建一个数字对象,实际上需要将其向下转换为 number*,尽管对象事先已知为 number。

我用了一种笨拙的方式解决了这个问题:函数 myset 会测试所有支持的 typeid 值以获取正确的动态转换。它是一系列嵌套的 if 语句,执行 typeid 检查。

除了繁琐之外,该函数仅适用于调用“set”方法,应定义类似的函数以调用其他方法。如果我能够自动转换到我指向的对象类型,那么一切都会简单得多。

这种方法的缺点包括:

  1. 代码重复:如果我定义了另一个函数(例如 T get() {return val;}),我需要另一个带有完整嵌套 if 的 myget 函数!
  2. 支持的类型列表必须通过嵌套 if 调用明确定义
  3. 代码可能效率低下

编译器知道 lst[0](在下面的代码中)指向一个数字对象,尽管 lst 是一个元素对象的向量,从中派生了 number<> 对象。

是否有一种方法可以自动将定义为 base* 的指针向下转换为实际指向的对象的指针?

如果我有正确的向下转换,那么我就可以在 number<> 类中定义数千个方法,并使用 propercasting -> function(...) 调用它们。

以下是代码(它可以正常工作,核心是“myset”的定义)

谢谢,

Pietro M.

PS:我主要关注使用除 STL 之外的其他库(如 Boost)的标准 C++ 方法。

#include <iostream>
#include <vector>
#include <typeinfo>

using namespace std;

class element
{
public:
    virtual void print() = 0; // print is not templatized and works properly
    //template <class T> virtual set(T v) = 0; // this would solve all my problems, if only it were legal.
};

template <class T>
class number : public element
{
    T val;
public:
    void print() {cout << "number is " << val << endl;}
    void set(T v) {val = v;}
};

// That's the best I can do!
template <class T>
void myset(T v, element *ptr)
{
    // There is a kink in the template: the compiler checks the T type by the value of v, that in this case is an integer:
    // cout << "Type name for the template is: " << typeid(T).name() << endl;
    // cout << "Type name for an integer is:   " << typeid(int).name() << endl;

    if (typeid(*ptr) == typeid(number<double>))
    {
        ((number<double> *) ptr) -> set(7);
        return;
    }
    else if (typeid(*ptr) == typeid(number<float>))
    {
        ((number<float> *) ptr) -> set(7);
        return;
    }
    // add other types... (tedious)
    else
    {
        cout << "type not supported" << endl;
    }
}

int main()
{
    vector <element *> lst; // list of heterogeneous templatized objects
    lst.push_back(new number<float>);
    lst.push_back(new number<double>);

    lst[0] -> print();

    //((number<float> *) lst[0]) -> set(7); it's correct but it requires I know the type when I call it (tedious)

    myset(7, lst[0]); // may be inefficient, but it works (for the types which are explicitly supported)

    // cast_to_what_the_pointer_actually_points_to <lst[0]> -> set(7); // that's what I'd like to do: a downcast function which checks the object type and returns the correct pointer would be able to call any class method...

    lst[0] -> print();
}

那么,这些类型没有共同的接口(除了print之外)。为什么你要试图将它们视为这样呢?(或者,你可能想使用动态类型语言而不是C++) - R. Martinho Fernandes
我希望它们使用多态拥有一个共同的接口,但是由于编译器不允许模板虚函数,在基类中无法定义 template <class T> virtual void set(T) = 0;。我找不到在 element 中定义虚函数 set 的方法,所以我希望我可以规避这个限制。 - pietrom79
1
不,那看起来不像你想要的。你不希望所有子类都有接受任何和所有类型的函数(这就是模板虚函数的意思,也是为什么它不被允许的原因)。解决这个问题的第一步是确定你想从中得到什么。 - R. Martinho Fernandes
1个回答

3

你已经接近成功了。你需要有一个模板化的set方法,该方法调用一个私有虚拟方法并传递typeid和指向其参数的void *指针,允许重写决定如何处理它:

class element
{
    virtual void set_impl(const std::type_info &, const void *) = 0;
public:
    template <class T> void set(T v) { set_impl(typeid(v), &v); }
};

如何编写set_impl的示例:

template <class T>
class number : public element
{
    T val;
    void set_impl(const std::type_info &ti, const void *pv) {
        if (ti == typeid(T)) {
            val = *static_cast<const T *>(pv);
        } else {
            throw std::invalid_argument("incorrect type to set()");
        }
    }
};

这类似于 Boost.Any 采用的方法。

非常感谢。我尝试让它工作,但出现了问题。如果我调用set函数,编译器将从参数v中推导模板类型,而不是派生对象。如果我调用 lst[0] -> set(7);模板参数将是int,因为'7'被解释为这样。因此,编译器将调用number<int>的方法,而不是lst[0]指向的类。我尝试通过使用typeid(*this)调用set_impl来解决问题,但是存储为整数的v被解释为浮点数,提供了一个无意义的结果。无论如何,我已经很接近了。谢谢! - pietrom79

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接