7
votes

I searched around the web but I haven't found an answer yet, as to why I get this error:

Error 1 error LNK2019: unresolved external symbol "public: class Mesh * __thiscall AssetManager::GetAsset(class std::basic_string,class std::allocator >)" (??$GetAsset@PAVMesh@@@AssetManager@@QAEPAVMesh@@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "public: void __thiscall SceneManager::AddMesh(class std::basic_string,class std::allocator >)" (?AddMesh@SceneManager@@QAEXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) C:\Users\Dirk\documents\visual studio 2010\Projects\OpenGameEngine\OpenGameEngine\SceneManager.obj

Here is my code:

AssetManager.h

#pragma once
#include <string>
#include <map>
#include "Asset.h"
#include "Mesh.h"

using namespace std;

class AssetManager
{
    public:
        AssetManager(string rootFolder);
        bool LoadAsset(string assetName, int assetType, string assetFile, bool subDirectory);
        void UnloadAsset(string assetName);
        template <class T> T GetAsset(string assetName);
        bool AddAssetSubDirectory(int assetType, string subDirectory);  

    private:
        string m_rootFolder;
        map<int, string> m_assetSubs;
        map<string, Asset*> m_assets;
};

AssetManager.cpp

#include "AssetManager.h"

AssetManager::AssetManager(string rootFolder)
{
    m_rootFolder = rootFolder;
}

bool AssetManager::AddAssetSubDirectory(int assetType, string subDirectory)
{
    if (m_assetSubs.find(assetType) == m_assetSubs.end())
    {
        m_assetSubs[assetType] = subDirectory;
        return true;
    }
    else
    {
        return false;
    }
}

bool AssetManager::LoadAsset(string assetName, int type, string assetFile, bool subDirectory)
{
    string filePos;
    if (subDirectory)
    {
        filePos = m_rootFolder.append(m_assetSubs[type]).append(assetFile);
    }
    else
    {
        filePos = m_rootFolder.append(assetFile);
    }
    return true;
}

void AssetManager::UnloadAsset(string assetName)
{
    if (m_assets.find(assetName) != m_assets.end())
    {
        m_assets.erase(assetName);
    }
}

template <class T> T AssetManager::GetAsset(string assetName)
{
    if (m_assets.find(assetName) != m_assets.end())
    {
        return m_assets[assetName];
    }
    else
    {
        return null;
    }
}

SceneManager.h

#pragma once
#include <string>
#include <map>
#include "AssetManager.h"

using namespace std;

class SceneManager
{
    public:
    static SceneManager* Instance();
    void AddMesh(string assetName);
    void RemoveMesh(string assetName);
    void Draw();
    void Run();
    void SetAssetManager(AssetManager*);
    void Destroy();

    private:
    SceneManager();
    SceneManager(SceneManager const&);
    ~SceneManager();
    SceneManager& operator=(SceneManager const&){};
    static SceneManager* m_Instance;
    AssetManager *m_assetMgr;

    private:
    map<string, Mesh*> m_staticMeshes;
};

SceneManager.cpp

#include "SceneManager.h"
#include "AssetManager.h"

SceneManager* SceneManager::m_Instance = NULL;

SceneManager::SceneManager()
{
    m_assetMgr = 0;
}

SceneManager::SceneManager(SceneManager const&)
{

}

SceneManager::~SceneManager()
{
    delete m_assetMgr;
    m_assetMgr = 0;
}

void SceneManager::Destroy()
{
    delete m_Instance;
    m_Instance = 0;
}

SceneManager* SceneManager::Instance()
{
    if (!m_Instance)
        m_Instance = new SceneManager();

    return m_Instance;
}

void SceneManager::SetAssetManager(AssetManager *am)
{
    m_assetMgr = am; 
}

void SceneManager::AddMesh(string assetName)
{
    m_assetMgr->GetAsset<Mesh*>(assetName);
}

void SceneManager::RemoveMesh(string assetName)
{
    if (m_staticMeshes.find(assetName) != m_staticMeshes.end())
    {
        m_staticMeshes.erase(assetName);
    }
}

void SceneManager::Draw()
{
    for (map<string, Mesh*>::Iterator it = m_staticMeshes.begin(); it != m_staticMeshes.end(); ++it)
    {
        it->second->Draw();
    }
}

void SceneManager::Run()
{

}

Thanks in advance for the responses!

4

4 Answers

14
votes

C++ does not allow you to declare a template in a header file and define it in a .cpp file. The reason is that templates can only be created when the template parameters are known and so they can't be complied in advance.

To solve your problem, you will need to declare and define template <class T> T GetAsset(string assetName) in the AssetManager.h file

3
votes

Template methods must be implemented in header files, not in CPP.

Templates are just a kind of "macro". When you use the method GetAsset<Mesh*> in your SceneMagager.cpp file, the C++ compiler searches in that compilation unit the source code for GetAsset() in order to substitute the T typename with Mesh and compile the new method (create on the fly with that substituion). But SceneManager.cpp only knows about AssetManager.h (not the .cpp where GetAsset<T> is implemented) so the real code is not available and the compilation fails.

Just move your AssetManager::GetAsset implementation from the .cpp file to the .h and it should work.

1
votes

As your error message says, linking your object files together fails, because the function AssetManager::GetAsset<Mesh*> is not available.

Consider the following simple conundrum:
When compiling SceneManager.cpp, the compiler sees that AssetManager::GetAsset<Mesh*> is used and therefore adds a reference to it in the object file. However, since the definition is not available, it cannot actually instantiate the function template.
When compiling AssetManager.cpp, the compiler sees the definition of the function template, but no reason to instantiate it for any T at all.

To deal with this problem just get in the habit of immediately defining templates when declaring them - in your case, move the definition of AssetManager::GetAsset to its declaration in AssetManager.h.

-1
votes

You dont need to move the source into the header file. There is a shortcut maybe not the most beautiful but it works. You can add a function in AssetManager.cpp that basicly calls it self with GetAsset < Mesh* > (...). Then the function is solved. Its a sort of template specialization. Make the in-paramaters for that "special" call do nothing.

And remember to include Mesh.h in the cpp-file not the h-file or you'll get nested includes which leeds to a mess.

Regards Mattias