代码之家  ›  专栏  ›  技术社区  ›  andygeers

C++“this”与调用的对象方法不匹配

  •  6
  • andygeers  · 技术社区  · 17 年前

    我有一个这样的类层次结构(就像现在一样——例如,在真实的代码中没有多重继承):

    class CWaitable
    {
    public:
        void WakeWaiters() const
        {
            CDifferentClass::Get()->DoStuff(this);  // Breakpoint here
        }
    };
    
    class CMotion : public CWaitable
    {
       virtual void NotUsedInThisExampleButPertinentBecauseItsVirtual() { }
    };
    
    class CMotionWalk : public CMotion
    { ... };
    
    void AnnoyingFunctionThatBreaks(CMotion* pMotion)
    {
        pMotion->WakeWaiters();
    }
    

    我不会只是疯了吧?这很奇怪,对吧?

    6 回复  |  直到 17 年前
        1
  •  10
  •   Michael Kohne    17 年前

    如果它工作正常(即如果这没有产生崩溃,并且对象外部没有指针),那么我就不会太担心了。

        2
  •  6
  •   Naveen    17 年前

    class Test
    {
    public:
        virtual void f()
        {
    
        }
    };
    
    class CWaitable 
    {
    public:
        void WakeWaiters() const
        {
            const CWaitable* p = this;
        }
    };
    
    class CMotion : public CWaitable, Test
    { };
    
    
    class CMotionWalk : public CMotion
    {
    public:
     };
    
    
    
    void AnnoyingFunctionThatBreaks(CMotion* pMotion)
    {
        pMotion->WakeWaiters();
    }
    

        3
  •  2
  •   Pete Kirkham    17 年前

    wikipedia article on thunking 。如果将调试器设置为逐步遍历汇编代码,则应该会看到它发生。(无论是thunk还是简单地更改偏移量,都取决于你从给出的代码中省略的细节)

        4
  •  1
  •   D.Shawley    17 年前

    如果没有使用虚拟方法,则指向对象的指针指向对象的数据。一旦引入了虚拟方法,编译器就会插入一个虚拟查找表(vtable),指针就会指向该表。我可能遗漏了一些东西(我的大脑还没有工作),因为在基类中插入数据成员之前,我无法实现这一点。如果基类有一个数据成员,而第一个子类有一个虚成员,那么偏移量会因vtable的大小而异(在我的编译器上为4)。这里有一个例子清楚地表明了这一点:

    template <typename T>
    void displayAddress(char const* meth, T const* ptr) {
        std::printf("%s - this = %08lx\n", static_cast<unsigned long>(ptr));
        std::printf("%s - typeid(T).name() %s\n", typeid(T).name());
        std::printf("%s - typeid(*ptr).name() %s\n", typeid(*ptr).name());
    }
    
    struct A {
        char byte;
        void f() { displayAddress("A::f", this); }
    };
    struct B: A {
        virtual void v() { displayAddress("B::v", this); }
        virtual void x() { displayAddress("B::x", this); }
    };
    struct C: B {
        virtual void v() { displayAddress("C::v", this); }
    };
    
    int main() {
       A aObj;
       B bObj;
       C cObj;
    
       std::printf("aObj:\n");
       aObj.f();
    
       std::printf("\nbObj:\n");
       bObj.f();
       bObj.v();
       bObj.x();
    
       std::printf("\ncObj:\n");
       cObj.f();
       cObj.v();
       cObj.x();
    
       return 0;
    }
    

    在我的机器(MacBook Pro)上运行此程序会打印以下内容:

    aObj:
    A::f - this = bffff93f
    A::f - typeid(T)::name() = 1A
    A::f - typeid(*ptr)::name() = 1A
    
    bObj:
    A::f - this = bffff938
    A::f - typeid(T)::name() = 1A
    A::f - typeid(*ptr)::name() = 1A
    B::v - this = bffff934
    B::v - typeid(T)::name() = 1B
    B::v - typeid(*ptr)::name() = 1B
    B::x - this = bffff934
    B::x - typeid(T)::name() = 1B
    B::x - typeid(*ptr)::name() = 1B
    
    cObj:
    A::f - this = bffff930
    A::f - typeid(T)::name() = 1A
    A::f - typeid(*ptr)::name() = 1A
    C::v - this = bffff92c
    C::v - typeid(T)::name() = 1C
    C::v - typeid(*ptr)::name() = 1C
    B::x - this = bffff92c
    B::x - typeid(T)::name() = 1B
    B::x - typeid(*ptr)::name() = 1C
    

    bObj cObj A B C 不同之处在于 typeid(T) typeid(*ptr) 不同之处在于 B::x 当它被虚拟调用时。您还可以通过以下方式看到尺寸增加 sizeof 一旦插入虚拟表。

    CWaitable::WakeWaiters

    这将有助于消除这样的小意外。

        5
  •  0
  •   anon anon    17 年前

    #include <iostream>
    using namespace std;
    
    struct A {
        char x[100];
        void pt() {
            cout << "In A::pt this = " << this << endl;
        }
    };
    
    struct B : public A { 
        char z[100];
    };
    
    void f( A * a ) {
        cout << "In f ptr = " << a << endl;
        a->pt();
    }
    
    int main() {
        B b;
        f( &b );
    }
    
        6
  •  0
  •   andygeers    17 年前

    我无法解释为什么这有效,但将CWaitable::WakeWaiters声明为虚拟解决了这个问题