Error linking when inheriting generic/template class

I am writing a object management library that is meant to service objects in different libraries with different string coding classes so that they can interface with each other. As a result, I have created a core library with this template/generic class which represents a named object that uses a specific library's string class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename LibraryStringType>
class IIMOECORE_EXPORT NamedLibraryObject : public NamedObject
{
protected:
	LibraryStringType _name;
	inline NamedLibraryObject(const LibraryStringType& name, __uint32 referenceID) :
		NamedObject(referenceID), _name(name) { }

public:
	inline const LibraryStringType libraryFormattedName() const { return _name; }
	virtual ~NamedLibraryObject();
	
//interface:
	virtual void setName(const LibraryStringType& newName) = 0;
};


I then have a class in an "implementation" library (separate project) that inherits from this class using Qt:
1
2
3
4
5
6
7
8
9
10
11
12
class IIMOAPI_EXPORT IIMObject : public NamedLibraryObject<QByteArray>
{
public:
	inline IIMObject(const QByteArray& name, __uint32 referenceID) : 
		NamedLibraryObject(name, referenceID) { }
	static const __uint32 calculateHashValue(const __standardChar* name, __uint32 referenceID);

public overrides:
	virtual const __standardChar* name() const override;
	virtual const __uint32 calculateHashValue() override;
	virtual void setName(const QByteArray& name) override;
};


The problem is that when I compile this, I get linking errors for missing constructors for the NamedLibrary<QByteArray> class. I presumed that the specific implementation would be provided by the generic class itself. Is this not true? What am I missing?
Last edited on
The problem is that when I compile this, I get linking errors for missing constructors for the NamedLibrary<QByteArray> class.

Which constructors? Be specific.

You need to be specific with the constructors. You can contact my discord: Munchkin#4645 and I'm sure I can help you out with it and review your code.
I thought my description was enough... but ok. It is giving me linking errors as though the

NamedLibraryObject::NamedLibraryObject<QByteArray>(QByteArray const &,unsigned int) constructor and destructor don't exist (are not found by the linker); as well as the copy constructors. It's like it is expecting me to supply the entire class code... as if it wasn't a template.

jjojehong wrote:
You can contact my discord: Munchkin#4645 and I'm sure I can help you out with it and review your code.
Alright. Thanks. Though the code above is pretty much verbatim minus a few comments.

EDIT: it is also listing the assignment operators as not found (operator)=. Is this an issue? I wasn't intending on overloading the assignment operators.
Last edited on
Try adding the <QByteArray> arguments on line 5 in the constructor.
Ooops... should have caught that. I also added a destructor since the base class has a virtual destructor (though nothing really needs to be deleted at this level). That got rid of two linking errors... but one copy constructor, one assignment operator, and both the constructor and destructor are still not found (before, there was one additional copy constructor and one additional assignment operator missing).
Please post your actual error and warning messages. What you think is obvious to us may actually be ambiguous. Let's remove the ambiguity.

Also, is NamedObject an abstract base class? What functions are pure virtual? Maybe you can post the header for that class too.

Where is NamedLibraryObject<LibraryStringType>::~NamedLibraryObject() defined? It needs to be in (or included by) the header file or it will not be instantiated and compiled.

What is the definition of calculateHashValue()? Could it be silently calling the copy constructor and assignment operator? Do your error messages say where (file and line number) the missing symbols are required?

There are a lot of unknowns in what you have given us. Maybe repost all of your current code (verbatim--don't bother removing comments unless they are proprietary) as well as the error messages from the linker. Whole files will allow us to map line number from the error messages to actual code.




The base class of all classes (ManagedObject) does not have a destructor. Named object has a virtual destructor and all destructor definitions exist in the cpp file while being declared in the .h file. I also have some blank #defines which are meant to be comments (such as inl_props). I removed commented out functions. I included all relevant classes.

From the core dll. All objects are within a specific namespace named IIMOEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
	class IIMOECORE_EXPORT ManagedObject
	{
	protected:
		union {
			ID_STRUCT _id_properties;
			__uint64 _id;
		};

	protected inl_props:
		inline ManagedObject(__uint32 referenceID);

	protected interfaces:
		virtual void init();
		virtual const bool keyed() const;
		virtual void finalize();

	public inl_props:
		inline const bool isFinalized() const { return (_id_properties._referenceID & 0x1) != 0; }
		inline __uint32 referenceID() { return _id_properties._referenceID & REFERENCE_MASK; }
		inline __uint32 objectID() { return _id_properties._objectID; }
		inline __uint64 id() { return _id; }

	public operators:
		inline bool operator==(const ManagedObject& o) { return _id == o._id; }
	};

	/// LIBRARY INDEPENDENT ABSTRACT CLASS/INTERFACE FOR ALL NAMED OBJECTS
	/// Does not take a string since string data is library specific
	class IIMOECORE_EXPORT NamedObject : public ManagedObject
	{
	private:
		__uint32 _hashValue;

	protected:// inl_props:
		inline NamedObject(__uint32 referenceID) : ManagedObject(referenceID) { };
		inline const __uint32 hashID() const { return _hashValue; }
		friend class IObjectManager;
	
	protected overrides:
		virtual void init() override;
		virtual void finalize() override;

	public interfaces:
		virtual ~NamedObject();
		virtual const __standardChar* name() const = 0;
		virtual const __uint32 calculateHashValue() = 0;
	};

	/// Generic class for all named objects
	/// Meant to be used as the base class for all library dependent named objects
	template <typename LibraryStringType>
	class IIMOECORE_EXPORT NamedLibraryObject : public NamedObject
	{
	protected:
		LibraryStringType _name;

	protected inl_props:
		inline NamedLibraryObject(const LibraryStringType& name, __uint32 referenceID) : NamedObject(referenceID), _name(name) { }

	public inl_props:
		inline const LibraryStringType libraryFormattedName() const { return _name; }
		
	public:
		virtual ~NamedLibraryObject();
	
	public interfaces:
		//virtual void setLibraryFormattedName(const LibraryStringType& name);
		virtual void setName(const LibraryStringType& newName) = 0;
	};

#include "IObjectManager.h"
	inline IIMOEngine::ManagedObject::ManagedObject(__uint32 referenceID) : _id_properties(ID_STRUCT(referenceID, IObjectManager::globalInstance()->createID(referenceID))) { }


From the API dll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	// Base class for any standard Qt based Engine object
	class IIMOAPI_EXPORT IIMObject : public NamedLibraryObject<QByteArray>
	{
	public:
		inline IIMObject(const QByteArray& name, __uint32 referenceID) : NamedLibraryObject<QByteArray>(name, referenceID) { }
		static const __uint32 calculateHashValue(const __standardChar* name, __uint32 referenceID);
		~IIMObject();

	public overrides:
		virtual const __standardChar* name() const override;
		virtual const __uint32 calculateHashValue() override;
		virtual void setName(const QByteArray& name) override;
		//virtual void setName(const QLatin1String& name)
	};


Errors:
Severity Code Description Project File Line Suppression State
Error LNK2019 unresolved external symbol "__declspec(dllimport) public: class IIMOEngine::NamedLibraryObject<class QByteArray> & __thiscall
IIMOEngine::NamedLibraryObject<class QByteArray>::operator=(class IIMOEngine::NamedLibraryObject<class QByteArray> const &)" (__imp_??4?$NamedLibraryObject@VQByteArray@@@IIMOEngine@@QAEAAV01@ABV01@@Z) referenced in function "public: class IIMOEngine::IIMObject & __thiscall IIMOEngine::IIMObject::operator=(class IIMOEngine::IIMObject const &)" (??4IIMObject@IIMOEngine@@QAEAAV01@ABV01@@Z) IIMOAPI D:\Developing\Projects\IIM\METAphor\IIMOAPI\coreobjecttypes.obj 1

Error LNK2019 unresolved external symbol "__declspec(dllimport) protected: __thiscall IIMOEngine::NamedLibraryObject<class QByteArray>::NamedLibraryObject<class QByteArray>(class QByteArray const &,unsigned int)" (__imp_??0?$NamedLibraryObject@VQByteArray@@@IIMOEngine@@IAE@ABVQByteArray@@I@Z) referenced in function "public: __thiscall IIMOEngine::IIMObject::IIMObject(class QByteArray const &,unsigned int)" (??0IIMObject@IIMOEngine@@QAE@ABVQByteArray@@I@Z) IIMOAPI D:\Developing\Projects\IIM\METAphor\IIMOAPI\coreobjecttypes.obj 1

Error LNK2019 unresolved external symbol "__declspec(dllimport) public: virtual __thiscall IIMOEngine::NamedLibraryObject<class QByteArray>::~NamedLibraryObject<class QByteArray>(void)" (__imp_??1?$NamedLibraryObject@VQByteArray@@@IIMOEngine@@UAE@XZ) referenced in function "public: virtual __thiscall IIMOEngine::IIMObject::~IIMObject(void)" (??1IIMObject@IIMOEngine@@UAE@XZ) IIMOAPI D:\Developing\Projects\IIM\METAphor\IIMOAPI\coreobjecttypes.obj 1

Error LNK2019 unresolved external symbol "__declspec(dllimport) public: __thiscall IIMOEngine::NamedLibraryObject<class QByteArray>::NamedLibraryObject<class QByteArray>(class IIMOEngine::NamedLibraryObject<class QByteArray> const &)" (__imp_??0?$NamedLibraryObject@VQByteArray@@@IIMOEngine@@QAE@ABV01@@Z) referenced in function "public: __thiscall IIMOEngine::IIMObject::IIMObject(class IIMOEngine::IIMObject const &)" (??0IIMObject@IIMOEngine@@QAE@ABV01@@Z) IIMOAPI D:\Developing\Projects\IIM\METAphor\IIMOAPI\coreobjecttypes.obj 1
Last edited on
I am confused, too.

Error 1 (assignment operator) and Error 2 (copy constructor): These are correctly generated based on C++ generation rules, but I don't understand why the same operator/constructor are not generated for the template class.

Error 3 (destructor): This one actually appears easy. The template class destructor is declared in the header file but never defined. The definition of the template class destructor must be visible at compile time so that it can be instantiated and used by the derived class destructor. You can't stick the implementation of the template class destructor into a .cpp file or something and have it available. It needs to be in (or included by) the header file that declares the template class.

Hopefully the NamedObject:~NamedObject destructor and IIMObject::~IIMObject destructor are defined somewhere. Because these are not template classes, these destructors can be in source files.

By the way, Since ManagedObject is a base class, it should also have a (no-op) virtual destructor.

Error 4 (2-argument constructor): You got me. Are you sure that this header file includes the NamedLibraryObject header file? Only seeing part of a header file, I can't see what you have included and what you have not. (Part of the reason I asked you to post whole files, although I didn't previously state it.)

Why not create a minimal program that demonstrates the problem? Take your core dll header and strip out everything except the constructors, destructors and data members of the classes we need. Then do the same thing with your API dll. Last, create a main.cpp that simply creates an IIMObject object and nothing more. That should give you the link errors you are seeing. If not, then something you cut out is causing the problem. When you are able to replicate the problem with the least amount of code, then maybe the error will be apparent. I'd rather look through 3 or 4 small files that have the problem than parts of large files that may or may not skip the problematic lines.
Last edited on
doug4 wrote:
Error 3 (destructor): This one actually appears easy. The template class destructor is declared in the header file but never defined.

I fixed 3 before I saw your post. Same error still exists.

I believe I have discovered the "issue". However, it doesn't make any sense to me why it is an issue.

In a few places on the internet it is stated that linking issues will occur if the types used with the template are not anticipated ahead of time and declared in the library that exports the template. This is because the linker looks for the declared instances of the template in the original library. Here is an article that tries to deal with this issue (though his suggestion isn't working for me... I tried making the functions inline; some of them already were as can be seen above): https://www.codeproject.com/Articles/5455/Exporting-Member-Templates-from-DLLs

I don't understand why that is though. Why can't the compiler/linker be forced to build the specific use in the new library? That way it it can link to the current project rather than try and find it in the original dll. It seems that the C++ standard would/should have fixed this need a long time ago unless it isn't possible.




Last edited on
No one knows the answer?

Ok. Well, I was able to fix the specific issue above because for this particular project I was able to change the way the classes are arranged. I would still like to know if doing this is possible from an external library.

However... now I am having the same problem with a different set of classes in the same library. Before I get into the details of the error... I just want to make sure what I am doing is actually ok to do (to make sure my linking errors aren't the result of some other issue).

Is it ok to embed a generic instance of the container class within the class the template argument is using? Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
class MyGenericClass
{
	//... whatever
};

class AnotherClass
{
	class EmbeddedClass : MyGenericClass<AnotherClass> // is this allowed?
	{
		//... whatever
	};
};
Last edited on
Yes, it's fine.

There is no special relationship between nested and enclosing classes, except that in some sense, the nested class is implicitly a friend of the enclosing class. This relationship grants accessibility only, and the classes are otherwise independent.
Last edited on
Alright then. I need help figuring out why I get a linker error here. Using a similar form to the code above, I got it to compile. Using the code below though, I get the linker error at the bottom.

from "engine" dll is the base allocator class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	class IIMOECORE_EXPORT IObjectAllocator
	{
	private:
		__uint32 _typeID;
		//__uint32 _size;
		friend class TypeAllocator;

	private inl_props:
		//const void* createNewObject();
		inline void setTypeID(__uint32 id) { _typeID = id; }

	protected interfaces:
		virtual const __uint32 objectSize() = 0;
		virtual const char* objectName() = 0;

	public inl_props:
		const __uint32 typeID() const { return _typeID; }

	public:
		IObjectAllocator(__uint32 typeID);

	public interfaces:
		virtual ManagedObject* createObjectRef() = 0;
		virtual void activateObjectRef(ManagedObject* object) = 0;
		virtual void invalidateObjectRef(ManagedObject* object) = 0;
		virtual const __uint32 poolCount() = 0;
		virtual const __uint32 allocatedCount() = 0;
	};


from the project. first, the allocator template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
	template <typename ObjectType>
	class ObjectAllocator : public IObjectAllocator
	{
	private:
		QStack<ManagedObject*> _pool;
		QHash<__uint64, ManagedObject*> _allocated;
		const char* _name_literal;

	public:
		ObjectAllocator(const char* name_literal) :
			IObjectAllocator(TypeAllocator::getTypeID(name_literal)),
			_name_literal(name_literal)
			//_size(size)
		{
			if (typeID() == TypeAllocator::TypeNotFound)
				throw "Object Type for Allocator does not exist";
		}
		~ObjectAllocator() { }

	protected overrides:
		const __uint32 objectSize() override { return sizeof(ObjectType); }
		const char* objectName() override { return _name_literal; }

	public overrides:
		virtual ManagedObject* createObjectRef() override
		{
			if (_pool.isEmpty())
				return static_cast<ManagedObject*>(malloc(this->objectSize()));
			else
				return _pool.pop();
		}
		void activateObjectRef(ManagedObject* object) override
		{
			_allocated.insert(object->id(), object);
		}
		void invalidateObjectRef(ManagedObject* object) override
		{
			_allocated.remove(object->id());
			_pool.push(object);
		}
		const __uint32 poolCount() override { return _pool.size() + _allocated.size(); }
		const __uint32 allocatedCount() override { return _allocated.size(); }
	};


Now for the class that gives the linking error when it is compiled (note... both this class are the above class are not fully realized. This is just a compilation test to make sure the strategy I am planning on using will work).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	class TestObject : IIMObject
	{
	private:
		static int _typeID;
		inline static const int objectTypeID() { return _typeID; }
		inline static const char* objectClassName() { return "TestObject"; }
		inline static void setTypeID(int value) { _typeID = value; }

		class Allocator : public ObjectAllocator<TestObject> { Allocator(); };
		static Allocator _allocator; // causing link error?
		
	public:
		TestObject(const QByteArray& name, __uint32 resourceID);
		~TestObject();

	public operators:
		void* operator new(size_t size);
		void operator delete(void* p);

	public overrides:
		virtual const char* typeName() const;
		virtual const __uint32 typeID() const;
	};


When the three classes are built within the project, I get the error:
Severity Code Description Project File Line Suppression State
Error LNK2001 unresolved external symbol "private: static class SIMILE::TestObject::Allocator SIMILE::TestObject::_allocator" (?_allocator@TestObject@SIMILE@@0VAllocator@12@A) SIMILEAPI D:\Developing\Projects\IIM\METAphor\SIMILEAPI\TestObject.obj 1

> static Allocator _allocator; // causing link error?
¿did you instantiate it?
http://www.cplusplus.com/forum/general/113904/#msg622050
>¿did you instantiate it?
If you mean the constructor, TestObject::Allocator(), then yes; in the cpp file. Otherwise, it isn't a pointer. It should instantiate using the default constructor for TestObject::Allocator():

Do I still need to do this explicitly? I thought I would only need to do that with pointers.


Apparently I do...
Last edited on
A static data member needs to be explicitly instantiated. The whole point of a static data member is that it's not part of any single object; it's shared between all objects of the same class. Therefore, it won't be instantiated when any particular object is instantiated, and you have to do it explicitly.
Topic archived. No new replies allowed.