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

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

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

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>(),但是有没有办法以某种方式以某种方式提取调用者的类型?

如果您想避免 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>();
    }
};

没有神奇的方法来获取调用者的类型,但您可以使用 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 中定义但没有实现。这样您就可以直接从界面触发操作。虽然也许你在你的真实代码中有它,但在你的例子中没有。无论如何,这个解决方案与此完美兼容。

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

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

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 派生的 class derived_once_more 调用 foo(),它仍将使用类型 derived*

Online demo

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

您可以使用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)>( ); } 
};

Online demo

编辑:其他选择

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

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

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

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();
}

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

只要需要专门化的函数很少,这就可以正常工作。
在这种情况下,只有一个函数,所以它很好地解决了这个问题。


你可以添加一个static_assert来对T添加一个约束,例如:

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

需要包含 <type_traits> header.