如何在类级别存储从构造函数收集的类型信息,以便在强制转换中使用

How to store type information, gathered from a constructor, at the class level to use in casting

本文关键字:信息 转换 类型 存储 构造函数      更新时间:2023-10-16

我正在尝试编写一个类,我可以存储和使用类型信息,而不需要模板参数。

我想这样写:

class Example
{
public:
    template<typename T>
    Example(T* ptr)
        : ptr(ptr)
    {
        // typedef T EnclosedType; I want this be a avaialable at the class level.
    }
    void operator()()
    {
        if(ptr == NULL)
            return;
        (*(EnclosedType*)ptr)(); // so i can cast the pointer and call the () operator if the class has one.
    }
private:
    void* ptr;
}

我不是在问如何编写一个is_functor()类。

我想知道如何在构造函数中获取类型信息并将其存储在类级别。如果这是不可能的,一个不同的解决方案将是赞赏的。

我认为这是一个很好的和有效的问题,但是,除了在类级别使用模板参数之外,没有一般的解决方案。你在问题中试图实现的——在函数中使用typedef,然后在整个类中访问它——是不可能的。

类型擦除

只有当你对构造函数参数施加某些限制时,才有一些替代方法。在这方面,这里有一个类型擦除的示例,其中某些给定对象的operator()存储在std::function<void()>变量中。

struct A
{
    template<typename T>
    A(T const& t) : f (std::bind(&T::operator(), t)) {}
    void operator()() const
    {
        f();
    }
    std::function<void()> f;
};
struct B
{
    void operator()() const
    {
        std::cout<<"hello"<<std::endl;
    }
};
int main()
{
    A(B{}).operator()();  //prints "hello"
}

演示

但是,请注意这种方法的假设:假设所有传递的对象都有一个给定签名(这里是void operator())的操作符,该操作符存储在std::function<void()>中(关于存储成员函数,请参阅这里)。

从某种意义上说,类型擦除类似于"没有基类的继承"——可以为所有构造函数形参类使用带有虚括号操作符的公共基类,然后将基类指针传递给构造函数。
struct A_parameter_base
{
    void operator()() const = 0;
};
struct B : public A_parameter_base
{
    void operator()() const { std::cout<<"hello"<<std::endl; }           
};
struct A
{
    A(std::shared_ptr<A_parameter_base> _p) : p(_p) {}
    void operator()()
    {
        p->operator();
    }
    std::shared_ptr<A_parameter_base> p;
}

这与您的问题中的代码相似,只是它不使用void指针,而是指向特定基类的指针。

两种方法,类型擦除和继承,在它们的应用程序中是相似的,但是类型擦除可能在摆脱公共基类时更方便。但是,继承方法还有一个优点,那就是可以通过多个分派

恢复原始对象。

这也显示了两种方法的局限性。如果您的操作符不是void,而是返回一些未知的变化类型,则不能使用上述方法,而必须使用模板。继承的平行点是:你不能有虚函数模板。

实际的答案是在std::function<void()>中存储类的副本或std::ref包装的类的伪引用。

std::function类型擦除它存储的东西归结为3个概念:复制,销毁和调用一个固定的签名。(还有cast- to-original-type和typeid,更隐晦)

它所做的是在构造时记住如何对传入的类型执行这些操作,并以能够对其执行这些操作的方式存储副本,然后忘记该类型的其他所有内容。

你不能用这种方式记住一个类型的所有信息。但是,几乎任何具有固定签名的操作,或者可以通过固定签名操作进行中介的操作,都可以被类型擦除到。

第一种典型的方法是为这些操作创建一个私有纯接口,然后创建一个模板实现(在传递给tor的类型上模板化),该模板实现该特定类型的每个操作。执行类型擦除的类然后存储一个指向私有接口的(智能)指针,并将其公共操作转发给它。

第二种典型的方法是存储void*或char类型的缓冲区,以及一组指向实现这些操作的函数的指针。指向函数的指针既可以本地存储在类型擦除类中,也可以存储在为每个擦除类型静态创建的helper结构中,并且指向helper结构的指针存储在类型擦除类中。存储函数指针的第一种方式类似于c风格的对象属性,第二种方式类似于手动虚函数表。

在任何情况下,函数指针通常采用一个(或多个)void*,并知道如何将它们转换回正确的类型。它们是在知道类型的actor中创建的,或者作为template函数的实例,或者作为本地无状态lambda,或者间接地。

你甚至可以将两者混合使用:静态pimpl实例指针采用void*或其他类型。

通常使用std::function就足够了,与使用std::function相比,手动书写类型擦除很难正确。

前两个答案的另一个版本-更接近您当前的代码:

class A{
public:
  virtual void operator()=0;
};
template<class T>
class B: public A{
public:
  B(T*t):ptr(t){}
  virtual void operator(){(*ptr)();}
  T*ptr;
};
class Example
{
public:
    template<typename T>
    Example(T* ptr)
    : a(new B<T>(ptr))
    {
        // typedef T EnclosedType; I want this be a avaialable at the class level.
    }
    void operator()()
    {
        if(!a)
            return;
        (*a)();
    }
private:
    std::unique_ptr<A> a;
}