问题 在抽象基类上使用__declspec(novtable)会以任何方式影响RTTI吗?


或者,使用__declspec(novtable)还有其他已知的负面影响吗?我似乎无法找到任何问题的参考。


6304
2017-11-26 02:33


起源

另见我的相关问题 stackoverflow.com/questions/1787752 - paxos1977


答案:


MSCV使用 one vptr per object and one vtbl per class 实现RTO和虚函数等OO机制。
因此,当且仅当vptr设置正确时,RTTI和虚函数才能正常工作。

struct __declspec(novtable) B {
    virtual void f() = 0;
};
struct D1 : B {
    D1() {
    }       // after the construction of D1, vptr will be set to vtbl of D1.
};
D1 d1;      // after d has been fully constructed, vptr is correct.
B& b = d1;  // so virtual functions and RTTI will work.
b.f();      // calls D1::f();
assert( dynamic_cast<D1*>(&b) );
assert( typeid(b) == typeid(D1) );

B在使用时应该是抽象类 __declspec(novtable)
除了D1的构造函数之外,不会有B的实例。
并且__declspec(novtable)在大多数情况下没有负面影响。

但在派生阶段的建设 __declspec(novtable) 将使它与ISO C ++语义不同。

struct D2 : B {


    D2() {  // when enter the constructor of D2 \  
            //     the vtpr must be set to vptr of B \
            //     if  B didn't use __declspec(novtable).
            // virtual functions and RTTI will also work.

            this->f(); // should calls B::f();
            assert( typeid(*this) == typeid(B) );
            assert( !dynamic_cast<D2*>(this) );
            assert( dynamic_cast<B*>(this) );

            // but __declspec(novtable) will stop the compiler \
            //    from generating code to initialize the vptr.
            // so the code above will crash because of uninitialized vptr.
    }
};

注意:虚拟f() = 0;使f成为一个 pure virtual function 和B是一个抽象类。
definition 一个纯虚函数 could (不 must)失踪。
C ++允许在构造函数中调用虚函数,我们不建议这样做。

更新: D2中的错误:派生构造函数中的vptr。

struct D3 : B {  // ISO C++ semantic
    D3() {       // vptr must be set to vtbl of B before enter
    }            // vptr must be set to vtbl of D2 after leave
};

但是vptr在构造过程中是不确定的。这是构造函数中不建议使用虚函数调用的原因之一。

如果D2 :: D2()中的vptr为B并且缺少B :: f()的定义, this->f(); 在vtbl中取消引用指向函数时会崩溃。
如果D2 :: D2()中的vptr是B而B使用novtable, this->f(); 取消引用未初始化的vptr时会崩溃。

实际上,D2 :: D2()中的vptr在MSVC(msvc8)中是D2。编译器在执行D2 :: D2()中的其他代码之前将vptr设置为D2。
所以 this->f(); 调用D2 :: f()并违反三个断言。


10
2017-12-02 21:49



基本上,这归结为(1)在构造函数中没有虚函数调用,以及(2)如果实际调用纯虚函数,则不再获得声明“称为纯虚函数”的错误信息。 - paxos1977


如果我理解正确的话: ctor或dtor中的任何虚拟fn调用都将转换为编译时链接。我们无法从(c / d)转发器进行虚拟fn调用。原因是当基类的对象被创建时,它不知道派生类,因此无法调用派生类,并且w.r.t应用相同的逻辑。


3
2018-02-23 16:22