0

This is one of those cases where I thought I understood C++ virtual methods reasonably well, and then an example comes along where I realise that, sadly, I don't. Is there anyone reading this who can make sense of the following?

Here is some test code in which I define a very simple base class (actually just a two-element struct), an abstract template class containing a virtual void method, and then a derived class which inherits from both of them and explictly overrides the virtual void method with a concrete method.

#include <string.h>  // For memcpy
#include <vector>    // For std::vector

struct int_array_C
{
    int  n;
    int* contents;
};

template <typename T> class array_template
{
public:
    array_template<T>() {}

    array_template<T>(const array_template<T> &source) 
    {
        *p_n = *(source.p_n);
        setPointers(&(source.local_contents[0]));
    }

    // ..and in reality, a bunch of other array manipulation functions

protected:
    virtual void setPointers(const T* data) = 0;

    int *p_n;
    std::vector<T> local_contents;
};

class int_array : public int_array_C, public array_template<int> 
{
public:
    int_array() : array_template<int>() 
    { 
        n = 0; contents = NULL; 
    }

protected:
    virtual void setPointers(const int* data)
    {
        p_n = &n;
        local_contents.resize(n);
        memcpy(static_cast<void *>(&local_contents[0]), 
               static_cast<const void *>(data), n*sizeof(int));
        contents = &local_contents[0];
    }
};

int main()
{
    int_array myArray;
    int_array yourArray(myArray);
    return 1;
}

When the copy constructor is called in the second line of main(), the argument is an instance of the derived class which has a concrete setPointers() method. Therefore, when the template class's copy constructor is called and the call to setPointers() is encountered, I'd expect the rules of polymorphism to kick in and the derived class's setPointers() method to be called.

In fact, however, the compiler chokes on this; at compilation time I get a warning saying

"Warning: call of pure virtual function at line 18" 

and at link time the linker fails with a message saying

error LNK2019: unresolved external symbol "protected: virtual void __cdecl array_template<int>::setPointers(int const *)" (?setPointers@?$array_template@H@@MEAAXPEBH@Z) referenced in function "public: __cdecl array_template<int>::array_template<int>(class array_template<int> const &)" (??0?$array_template@H@@QEAA@AEBV0@@Z)

Exactly the same thing happens (with slight variation in the text of the error messages) using Visual C++ and Intel C++ on Windows and gcc on Linux, so it's obviously a genuine violation of language rules rather than just being a compiler quirk. Yet I can't see what the problem is.

So, what am I doing wrong and how might I make this work as intended?

Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37
  • The code has a lot of errors dude. – 101010 Jun 19 '14 at 23:01
  • 2
    You can't call a derived class's virtual function from a base class constructor, because at the time it's called the derived class hasn't been constructed yet. – Crowman Jun 19 '14 at 23:04
  • the call is wrong, but i still don't understand why this ends up as a linking error rather than runtime UB – Cheers and hth. - Alf Jun 19 '14 at 23:07
  • I disagree with the assertion that this is a duplicate of the cited answer. There are indeed many things that can cause an undefined reference error at link time, but I can't find this one amongst those listed there. As discussed below (and nailed by @Paul Griffiths), the key to this problem is the order in which the base class methods and the derived class methods are instantiated, and I can't find anything about that in the cited answer. The FAQ referenced by Cheers and hth. Alf is much more relevant, but I don't think I'd even have found the answer there without Paul Griffiths' comment. – Eos Pengwern Jun 20 '14 at 06:31

3 Answers3

0

In the execution of a constructor body for a class T the dynamic type is T. Any direct or indirect call of a pure virtual T member function here, is therefore invalid. It's some extra type checking security of C++, ensuring that you don't accidentally get into derived class code before the derived class sub-object has been properly initialized (or at least given the chance).

However, I don't understand why it ends up as a linking error.

Regarding solutions, this is a FAQ; essentially you have to somehow pass the derived class functionality or data up to the base class, and there are many ways to do that. As it happens I once convinced Marshall to include that FAQ item. So I can to some degree claim it as my answer to your problem. :-)


Update on the original code behavior.

Adding the following:

template< class T >
void array_template<T>::setPointers( const T* data )
{
    using namespace std;
    clog << "!pure virtual setPointers called!" << endl;
}

it turned out that with both Visual C++ 12.0 and g++ 4.8.2 it was called.

The call is Undefined Behavior so both compilers are in their right to produce such behavior, but still it surprised me. I'd think instead they would put a pointer to some error function in the vtable. Anyway, it explains the linking errors, with these two compilers.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • The linker error is explained a bit more in [this SO answer](http://stackoverflow.com/questions/11437242/linker-error-on-pure-virtual-function-call-with-gcc). A pure virtual method can be defined as explained [here](http://stackoverflow.com/questions/2089083/pure-virtual-function-with-implementation) but I too wouldn't expect it to be called in this situation either. – uesp Jun 19 '14 at 23:27
  • @uesp: Well the question of why both compilers emit a call to an implementation in the call's class is what's baffling, and is not answered by David. The call is UB and trivially so, so they *can* do that. It's the impracticality of it and that they both do it, that baffles me; I don't understand it, but I feel that there is something to understand. – Cheers and hth. - Alf Jun 19 '14 at 23:33
  • Why wouldn't it be OK if the function was defined in another translation unit? – Crowman Jun 20 '14 at 00:00
  • @PaulGriffiths: Even if the class is a template the relevant specialization of it can be defined in another translation unit, there's no problem there. The problem is simply that a virtual call of a class T pure virtual function, when the dynamic type is T, is UB no matter whether the function is defined or not. And a practical way to handle that is to place an error-generating stub in the T vtable. The linking error is nice but as shown it can't be relied on, so it's less practical, just baffling. – Cheers and hth. - Alf Jun 20 '14 at 00:36
0

Paul Griffiths nailed it with his comment under my original question:

"You can't call a derived class's virtual function from a base class constructor, because at the time it's called the derived class hasn't been constructed yet."

That's the critical thing: the call occurs in the constructor, and because the base class's constructor is called before the derived class exists, there is no concrete version of setPointers to call yet (at least, not in the new object that is in the process of being created; the fact that the old object being passed as an argument has one is irrelevant here).

The solution is pretty simple: move the copy constructor out of the template altogether and place it within the derived class instead. It's only a two-line function so it's not too painful to do this with each of my dozen or so derived classes, and it solves the problem.

[N.B. If anyone is wondering what all this is in aid of, this is part of a large mixed-language application. I want to have a set of container functions which have the functionality of a std::vector when used in C++, so that I can resize, reference, copy etc. at will, but then pass to a plain C subroutine expecting just the base struct. Everything except the template copy constructor has been working well for a long time now!]

Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37
0

You cannot call virtual methods from base class constructors and get derived class methods: those are set up after the base class is constructed.

Try changing the layout of your inheritsnce:

class int_array_impl:public int_array_C{...};
template<class T,class B>class array_template:public B{
  //...
};
typedef array_template<int, int_array_impl> int_array;

make the method non-virtual, get rid of pure virtual stuff in array_template, and just blindlh invoke this->method. If B has it, it will be called.

Either forward constructors, or have a 4th class under array_template that provides custom ones.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524