Question

I'm using class templates which contain virtual functions in my current project, and I stumbled upon a problem I can't overcome on my own.

  1. Class templates cannot have their member function bodies split from class definition in .hpp file because of linker errors. I don't want to instantiate my templates for each new type I'm abut to use, so all that's left is to leave them inlined. This is absolutely fine as they are 1-2 lines long most of the time, so I'm not going to experience any code bloat.
  2. On the other hand, gcc creates vtable for a polymorphic class in .cpp file that has definition of the first non-inline function that is declared in the class definition. Since I have all member functions inline, I'm getting undefined reference to vtable, or no RTTI symbol found for my class in GDB.

Please consider the following code:

template <typename T>
struct Test
{
    virtual void testMe() const = 0;
    virtual ~Test() = default;
};

template <typename T>
struct test : public Test<T>
{
    virtual void testMe() const
    {
        std::cout << typeid(T).name() << std::endl;
    }
    virtual ~test() = default;
};

int main()
{
    test<int> t;
    Test<int>& T = t;

    T.testMe();

    return 0;
}

In this particular example I'm getting:

can't find linker symbol for virtual table for `test<int>' value

when debugging with GDB.

How do I force my compiler to put vtable in a specific cpp file when all class functions are inline?


EDIT:

Since the example provided above didn't illustrate the problem, here's my original code.

The class that's causing the problem:

#ifndef CONVERTIBLETO_H
#define CONVERTIBLETO_H

#include "convertibleTo_converters.h"
#include <functional>

template <
            typename IT,
            template <typename InterfaceType, typename ErasedType>
                class Converter = convertibleTo_detail::default_converter
         >
class convertibleTo
{
public:
    typedef convertibleTo<IT, Converter> this_type;
    typedef IT InterfaceType;

    struct is_type_eraser_tag {};
private:

    class holder_interface
    {
    public:
        virtual InterfaceType get() const = 0;
        virtual void set(const InterfaceType&) = 0;
        virtual holder_interface* clone() const = 0;

        virtual ~holder_interface() {}
    };


        template <typename ErasedType>
        class holder : public holder_interface
        {
        public:
            virtual InterfaceType get() const
            {
                return (Converter<InterfaceType, ErasedType>::convert(this->data));
            }
            virtual void set(const InterfaceType& value)
            {
                this->data = (Converter<InterfaceType, ErasedType>::convert(value));
            }
            virtual holder_interface* clone() const
            {
                return new holder(*this);
            }

            holder() = delete;
            holder(const holder& other):
                data(other.data)
            { }
            holder(ErasedType& d):
                data(d)
            { }

            virtual ~holder() = default;

        private:
            ErasedType& data;
        };
public:

    inline InterfaceType get() const
    {
        if (this->held)
            return this->held->get();
        else
            return InterfaceType();
    }

    inline void set(const InterfaceType& value)
    {
        if (this->held)
            this->held->set(value);
    }

    inline bool empty() const
    {
        return ! this->held;
    }

    convertibleTo<InterfaceType, Converter>& operator= (const convertibleTo<InterfaceType, Converter>& other)
    {
        if(this->held)
            delete this->held;
        this->held = other.held->clone();
        return *this;
    }

    convertibleTo():
        held(nullptr)
    { }

    template <typename T>
    explicit convertibleTo(T& data):
        held(new holder<T>(data))
    {
    }

    convertibleTo( convertibleTo& other ):
        convertibleTo( const_cast<const convertibleTo&>(other))
    {
    }

    convertibleTo( const convertibleTo& other ):
        held(nullptr)
    {
        if(other.held)
            this->held = other.held->clone();
    }

    ~convertibleTo()
    {
        if (this->held)
            delete this->held;
    }

private:
    holder_interface * held;
};

#endif

Required helper classes:

#ifndef CONVERTIBLETO_CONVERTERS_H
#define CONVERTIBLETO_CONVERTERS_H

#include <string>
#include <sstream>

namespace convertibleTo_detail
{
    template <typename InterfaceType, typename ErasedType>
    struct default_converter
    {
        static inline InterfaceType convert(const ErasedType& input)
        {
            return input;
        }

        static inline ErasedType convert(const InterfaceType& input)
        {
            return input;
        }
    };

    template <typename T>
    struct default_converter<T, T>
    {
        static inline T convert(const T& input)
        {
            return input;
        }
    };

    template <typename ErasedType>
    struct default_converter<std::string, ErasedType>
    {
        static inline std::string convert(const ErasedType& input)
        {
            default_converter<std::string, ErasedType>::prepareConverter();
            default_converter<std::string, ErasedType>::converter << input;
            return default_converter<std::string, ErasedType>::converter.str();
        }

        static inline ErasedType convert(const std::string& input)
        {
            default_converter<std::string, ErasedType>::prepareConverter(input);
            ErasedType result;
            default_converter<std::string, ErasedType>::converter >> result;

            return result;
        }

    private:

        static std::stringstream converter;

        struct SetExceptionFlagsOnce      
        {
            SetExceptionFlagsOnce()
            {
                default_converter<std::string, ErasedType>::converter.exceptions(std::stringstream::failbit);
            }
        };

        static void inline prepareConverter(std::string value = "")
        {
            static SetExceptionFlagsOnce setter;
            default_converter<std::string, ErasedType>::converter.clear();
            default_converter<std::string, ErasedType>::converter.str(value);
        }
    };

    template <typename ErasedType>
    std::stringstream default_converter<std::string, ErasedType>::converter;

    template <>
    struct default_converter<std::string, std::string>
    {
        static inline std::string convert(const std::string& input)
        {
            return input;
        }
    };
}

#endif // CONVERTIBLETO_CONVERTERS_H

main.cpp:

#include <iostream>
#include "convertibleTo.h"


int main()
{

    int I = 5;

    convertibleTo< std::string > i(I);

    std::cout << i.get() << std::endl;
    i.set("321");
    std::cout << i.get() << std::endl;

    return 0;
}

Error I'm getting is:

RTTI symbol not found for class 'convertibleTo<std::string, convertibleTo_detail::default_converter>::holder<int>'

it's shown when I go inside i.get(), and then inside holder's get().


EDIT: Moved the full source from pastebin here, as per suggestion


Since the last two comments suggested that this is a GDB bug, how do I check this myself next time?

  1. In case GDB complains about missing vtable - would confirming that I can access every virtual member through a reference to ABC initialized with derived class be enough to confirm that everything is OK?
  2. In case GDB complains about missing RTTI symbol - would calling typeid() on the reference to ABC initialized with derived class to be enough to confirm that the RTTI symbol is, in fact, present?
Était-ce utile?

La solution

Your code (the full version, with two header files and main.C) compiles and links without any errors for me, with gcc 4.8.3, with the default options (except for -std=c++11, to enable C++11 mode).

I even loaded the resulting executable into gdb. gdb swallowed it without any issues.

I don't see anything wrong here.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top