【发布时间】:2014-02-08 22:04:37
【问题描述】:
我在当前项目中使用包含虚函数的类模板,但我偶然发现了一个我自己无法克服的问题。
- 类模板不能将其成员函数体从类中分离出来 由于链接器错误,在 .hpp 文件中定义。我不想 为我即将使用的每种新类型实例化我的模板,所以所有 剩下的就是让它们内联。这绝对是 很好,因为它们大部分时间都是 1-2 行,所以我不去 体验任何代码膨胀。
- 另一方面,gcc 为多态类创建 vtable .cpp 文件,其中定义了第一个非内联函数 在类定义中声明。因为我有所有成员 内联函数,我得到对 vtable 的未定义引用,或者没有 在 GDB 中为我的班级找到 RTTI 符号。
请考虑以下代码:
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;
}
在这个特定的例子中,我得到:
can't find linker symbol for virtual table for `test<int>' value
使用 GDB 进行调试时。
当所有类函数都内联时,如何强制我的编译器将 vtable 放入特定的 cpp 文件中?
编辑:
由于上面提供的示例没有说明问题,这是我的原始代码。
导致问题的类:
#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
必需的辅助类:
#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;
}
我得到的错误是:
RTTI symbol not found for class 'convertibleTo<std::string, convertibleTo_detail::default_converter>::holder<int>'
当我进入 i.get(),然后进入 holder 的 get() 时显示。
编辑:根据建议将完整源代码从 pastebin 移至此处
由于最后两个cmets都提示这是GDB的bug,下次我自己怎么检查呢?
- 如果 GDB 抱怨缺少 vtable - 确认我可以通过对使用派生类初始化的 ABC 的引用来访问每个虚拟成员就足以确认一切正常吗?
- 如果 GDB 抱怨缺少 RTTI 符号 - 对使用派生类初始化的 ABC 的引用调用 typeid() 是否足以确认 RTTI 符号实际上存在?
【问题讨论】:
-
这是您从 GDB 获得的关于缺少符号的唯一消息吗?
-
通常它可以为模板(或常规)类提供内联代码定义,编译器应该将它们整理出来,不要在链接级别内联!
-
ideone.com/zy4rN1 适合我。
-
我没有在您的课程中找到任何 Rtti(typeid)。如果您收到警告,请忽略这种情况。另外请注意,外部链接 (paste.bin) 应该始终有效。否则,您可以获得反对票。不鼓励在 stackoverflow 中主要使用 paste.bin。所以在得到答案后编辑你的问题,这将是真正的问题
-
Unkwnown source of warning: "can't find linker symbol for virtual table for..." 的可能重复项(这是一个 GDB 错误,您的代码没有问题)
标签: c++ templates gdb inline member