从派生类调用时推断'this'指针类型?

Infer 'this' pointer type when called from derived class?

本文关键字:this 指针 类型 派生 调用      更新时间:2023-10-16

在基类中有一个方法,它需要传递给它的类型来进行一些与类型相关的操作(查找、大小和一些方法调用)。目前看起来是这样的:

class base
{
    template<typename T>
    void BindType( T * t ); // do something with the type
};
class derived : public base
{
    void foo() { do_some_work BindType( this ); } 
};
class derivedOther : public base 
{
    void bar() { do_different_work... BindType( this ); } 
};

但是,我想知道是否有一种方法可以获得调用者的类型,而不必传递这个,以便我的调用点代码变成:

class derived : public base
{
  void foo() { BindType(); } 
};

没有显式this指针。我知道我可以显式地将模板参数提供为BindType<derived>(),但是是否有一种方法可以以某种方式提取调用者的类型?

没有什么神奇的方法可以获得调用者的类型,但是您可以使用CRTP(正如注释所提到的)来自动化此行为,代价是代码的复杂性:

class base
{
    template<typename T>
    void BindType(); // do something with the type
};
template <class T>
class crtper : base
{
     void BindDerived { BindType<T>(); }
}
class derived : public crtper<derived>
{
    void foo() { do_some_work BindDerived(); } 
};
class derivedOther : public crtper<derivedOther>
{
    void bar() { do_different_work... BindDerived(); } 
};

编辑:我应该提到,我有点期望foo将是一个虚函数,在base中定义而不实现。这样,您就可以直接从界面触发操作。虽然你可能在你的实际代码中有,但在你的例子中没有。无论如何,这个解决方案与这个完全兼容。

Edit2:问题编辑后,编辑澄清解决方案仍然适用

如果您想避免使用BindType<derived>(),请考虑(我同意,这有点啰嗦)BindType<std::remove_reference<decltype(*this)>::type>();以避免传递参数。它在编译时得到解决,避免了运行时的惩罚。

class base
{
protected:
    template<typename T>
    void BindType() { cout << typeid(T).name() << endl; } // do something with the type
};
class derived : public base
{
public:
    void foo()
    {
        BindType<std::remove_reference<decltype(*this)>::type>();
    }
};

它不会像你期望的那样工作

foo()的结果可能与您期望的不同:

class derived : public base           // <= YOU ARE IN CLASS DERIVED
{
public:
    void foo() { BindType( this ); }  // <= SO this IS OF TYPE POINTER TO DERIVED
};

模板参数在编译时扣除,因此它将是derived*。如果从derived派生的类derived_once_more调用foo(),它仍然会使用类型derived*

在线演示

但是你可以去掉虚拟参数*

可以使用decltype(this)来表示变量的类型名。它仍然在编译时定义:

class base
{
public: 
    template<typename T>
    void BindType( ) 
    { 
         cout << typeid(T*).name()<<endl;  // just to show the type 
    }
    virtual ~base() {};                    // for typeid() to work 
};
class derived : public base
{
public: 
    void foo() { BindType<decltype(this)>( ); } 
};

在线演示

编辑:其他替代

由于模板参数需要在编译时而不是运行时提供,因此可以使用:

  • 模板参数扣除(你的第一个代码片段)
  • decltype (see above)
  • 如果你打算在所有的派生类中添加这个,你可以使用一个宏来完成它,使用上面提到的解决方案之一
  • 可以使用CRTP模式(已经在另一个答案中解释过了)。

避免CRTP中间类的可能解决方案如下:

class base {
    using func_t = void(*)(void *);
    template<typename T>
    static void proto(void *ptr) {
        T *t = static_cast<T*>(ptr);
        (void)t;
        // do whatever you want...
    }
protected:
    inline void bindType() {
        func(this);
    }
public:
    template<typename T>
    base(T *): func{&proto<T>} {}
private:
    func_t func;
};
struct derived1: base {
    derived1(): base{this} {}
    void foo() {
         // ...
        bindType();
    }
};
struct derived2: base {
    derived2(): base{this} {}
    void bar() {
        // ...
        bindType();
    }
};
int main() {
    derived1 d1;
    d1.foo();
    derived2 d2;
    d2.bar();
}

基本思想是利用派生类的构造函数中的this指针是所需类型的这一事实。
它们可以作为基类构造函数的参数传递,并用于专门化一个函数模板,该函数模板在幕后完成的脏工作。
一旦构造函数返回,派生类的类型实际上在基类中被擦除。无论如何,proto的专门化包含了这些信息,并且它可以将基类的this指针强制转换为正确的类型。

只要有几个函数需要专门化,这就可以很好地工作。
在这种情况下,只有一个函数,所以它很好地适用于这个问题。


您可以添加static_assert来在T上添加约束,例如:

template<typename T>
base(T *t): func{&proto<T>} {
    static_assert(std::is_base_of<base, T>::value, "!");
}

它要求包含<type_traits>