【问题标题】:Infer 'this' pointer type when called from derived class?从派生类调用时推断“this”指针类型?
【发布时间】:2016-10-21 18:53:58
【问题描述】:

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

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

【问题讨论】:

  • 这是一个典型的 CRTP 用例stackoverflow.com/questions/4173254/…
  • 在基类中使用typeid(*this)
  • @davmac typeid 并没有以我可以使用的方式给我类型,比如调用 sizeof(T) 或 T::SomeFunction() - 它只给我 typeid。
  • @Steven 对,但你的问题只是说你需要它来“一些类型到索引的查找代码”,为此它应该没问题。
  • @davmac 很公平 - 我会更新问题。

标签: c++ templates types type-deduction


【解决方案1】:

没有什么神奇的方法可以获取调用者的类型,但是您可以使用 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:问题编辑后,进行编辑以阐明该解决方案仍然适用。

【讨论】:

  • 我认为我的示例不太清楚—— foo 是一个简单的方法,定义在派生类中特定于派生类的方法; BindType 的使用在所有基类的派生类中并不一致。
  • @Steven 我的解决方案仍然有效,只需要更改事物的名称。查看更新的代码。
【解决方案2】:

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

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

【讨论】:

  • 我看不出这比在尖括号中明确指定 derived 有什么好处(如 OP 所述),除非您打算使用宏或复制和粘贴。
  • @NirFriedman 我认为同意 - 这更罗嗦。我怀疑大多数编译器会“做正确的事”并对其进行优化。
  • @Steven 好吧,我的担心与代码的性能无关。无论如何,OP的代码很可能也很可能得到完全优化(调用一次的函数通常会被内联,一旦函数被内联,游戏就结束了)。我真的只考虑代码的清晰度。
  • @NirFriedman 如果类型名称 (derived) 在给定上下文中易于访问,我认为没有任何优势。
  • decltype(*this)derived&amp;,而不是 derived
【解决方案3】:

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

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* 类型。

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

编辑:其他选择

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

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

【讨论】:

  • 是的 - 我同意 this 的类型特定于调用上下文,即使从派生类调用也是如此。我认为这没关系,因为我最担心的类型上下文比基础更大。这是一个公平的批评。 decltype(this) 当然会删除虚拟参数,但它需要在调用点输入更多内容。
  • 尽管提供的所有示例和解决方案都很有趣,但我实际上会使用我原来的解决方案! BindToType(this) 易于阅读,并且不需要现代 C++ 新手了解 decltype 为我们做了什么。 Christophe的解决方案涵盖了一些问题和解决方案,所以我认为它是一个好的答案的最佳选择。谢谢大家。
【解决方案4】:

避免 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, "!");
}

它需要包含&lt;type_traits&gt; 标头。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-19
    • 2023-03-14
    • 1970-01-01
    • 2014-02-15
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多