简体   繁体   中英

Use a forward-declared class in a virtual function in a template baseclass where the constructor only needs the forward declare?

I'm trying to find out why this fails. I'm using code that condenses down to basically what I have below. I have a simple class A that I'm specializing a template with. The template doesn't need this type to compile its constructor, and the constructor I'm actually calling (of a derived type) isn't exposed, so the compiler cannot generate the code for the constructor at this point.

GCC and Clang don't. MSVC however (2008 + 2010) do try to compile the virtual member and thereby do not compile.

Is this wrong from GCC and Clang, or from MSVC? Or am I bounding into UB territory?

class A;

template <typename X>
class S {
public:
    S() {}

    virtual int useX() { return X::value; }
};

class T : public S<A> {
public:
    T();
};

int main()
{
    new T();
    return 0;
}

When MSVC instantiates a class, it also populates its vtable and for that purpose instantiates all of its virtual functions, even those never called.

In your case, the function useX cannot be instantiated without compiler seeing the full definition of A.

If you declare useX as non-virtual, MSVC works fine.

It seems this behavior is compiler-dependent; for example, AIX is even more aggressive than MSVC in instantiating (unused) functions.

First, as many have stated, the standard fully allows for compilers to instantiate (or chose not to instantiate) to their hearts content in this situation; by a strict interpretation of the standard, this is a code issue, not an MSVC bug. The behavior you are depending on is platform specific and therefore non-standard; while this is not a "bug" per se, it's distinctly non-portable.

However, it's interesting to understand why GCC and Clang differ from MSVC, which has to do with how vtables are set up in each compiler.

A quick overview of how objects look in memory:

  1. no inheritance, all calls are static, no vtable or adjustments
  2. single inheritance, all calls are static, clever organization
  3. multiple inheritance, all calls are static, pointer adjustments
  4. virtual inheritance, calls can be dynamic, vtable and pointer adjustments

In the first case, all the information can be static; member functions are just normal functions with a hidden this. In the second case, the derived class (or classes, for a long inheritance list) can be cleverly put after the base class in memory, so that Derived* == Base*. This optimization doesn't work with multiple inheritance, obviously, which means each call needs to adjust the this pointer. Virtual inheritance then adds an array, the vtable, which dynamically selects the correct function at run time.

What does any of this have to do with instantiation of unused members?

In general, no matter what your implementation, you need three things:

  1. A delta, detailing where in your this the subobject you care about is
  2. a virtual index (vindex) detailing which virtual method to call
  3. an array of methods to call (vtable)

G++ has a standardized and well copied way to do these adjustments, used in many compilers (including Clang, as Clang is meant to be a drop in replacement for GCC):

  1. store the delta explicitly
  2. store vindex and offset calculations in the vtable as well, with method addresses on evens and vindex's on odds

This is a sort of strange cache optimization which has some benefits; it's been widely copied, despite not being the most elegant solution. It uses one structure for all cases, and does the same calculations each time, depending on compiler optimizations to replace virtual table hops with direct calls when applicable (a task that the GCC optimizer are marvelous at).

MSVC, on the other hand, is not merely a little inelegant. It's a horrible hack: it uses a different structure for each case. This allows it to avoid the overhead of unnecessary calculations, and to save space in many cases. However, this means that casting member function pointers causes them to change size, in the common case (derived to base) causing them to lose information .

Because of this, unlike the more elegant GCC implementation, MSVC absolutely must instantiate virtual member functions; if it doesn't, it could easily lose information between translation units or during linking, and have the same object represented by different sized structures !

This was such a problem they actually added keywords just to deal with it: http://msdn.microsoft.com/en-us/library/ck561bfk.aspx

So, while you don't think the compiler needs the type, MSVC certainly does, or it will almost certainly be unsafe.

Edit: confused myself with simple single inheritance, base class goes FIRST:

class A : public B

becomes

[[ B ] A ]

So the pointers *B == *A

From the draft C++1y standard: (via @DyP)

An implementation shall not implicitly instantiate a function template, a variable template, a member tem- plate, a non-virtual member function, a member class, or a static data member of a class template that does not require instantiation. It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be in- stantiated. The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.

emphasis mine. Any virtual function in a template can be instantiated by any C++ compiler for any or no reason at any point without violating the C++ standard. So MSVC is standards conforming in this matter.

This is 14.7.1.11 in the current draft N3797 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM