带有模板函数的未解决外部符号

10
我在网上搜索了一下,但是还没找到为什么会出现这个错误的答案:
错误1 错误 LNK2019:无法解析的外部符号“public:class Mesh *__thiscall AssetManager::GetAsset(class std :: basic_string> const &)”(?$ GetAsset@PAVMesh@@@AssetManager @@ QAEPAV Mesh @@ V?$ Basic_string@DU?$ Char_traits@D@std @@ V?$ Allocator@D@2@@std @@ @Z) 在函数“public:void __thiscall SceneManager :: AddMesh(class std :: basic_string> const &)”中引用(? 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
以下是我的代码:
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()
{

}

提前感谢您的回复!


1
可能是一个重复问题:为什么模板只能在头文件中实现? - Constructor
4个回答

19

C++不允许你在头文件中声明模板,然后在.cpp文件中定义它。原因是只有在知道模板参数时才能创建模板,所以无法预先编译。

为了解决这个问题,你需要在AssetManager.h文件中声明和定义template <class T> T GetAsset(string assetName)


您确实可以分别声明和定义函数模板。您只需要做对就行了。 - danielschemmel
1
分离的正确方式是什么?顺便说一下,尽管我之前尝试过,但分离还是起作用了。 - Wouter Standaert
@gha.st 是的,但那样做就违背了模板的初衷。 - Caesar
@Caesar,你如何定义两个相互递归的函数模板而不事先声明其中一个? - danielschemmel
1
@gha.st 你赢了这一轮。已编辑。 - Caesar
如果你使用显式模板实例化,你可以在头文件中声明一个模板,并在.cpp文件中定义它。 - Constructor

3

模板方法必须在头文件中实现,而不是在CPP文件中。

模板只是一种“宏”(macro)。当您在SceneMagager.cpp文件中使用GetAsset<Mesh*>方法时,C++编译器将在该编译单元中查找GetAsset()的源代码,以便用Mesh替换T类型并编译新的方法(根据此替换即时创建)。但是SceneManager.cpp仅了解AssetManager.h(而不是实现GetAsset<T>的.cpp文件),因此真正的代码无法使用,导致编译失败。

只需将AssetManager::GetAsset的实现从.cpp文件移动到.h文件中,就可以解决问题。


没有技术上的理由要求函数模板必须在头文件中定义。事实上,C++甚至不知道“头文件”的概念。 - danielschemmel
好的内联模板类可以在.cpp文件中创建,但是由于模板是定义,而定义通常放在头文件中,因此将它们放在那里是有意义的。 - serup

1
正如您的错误消息所述,链接您的目标文件失败,因为函数AssetManager :: GetAsset< Mesh * >不可用。
考虑以下简单的难题:
编译SceneManager.cpp时,编译器看到使用了AssetManager :: GetAsset< Mesh * >,因此在目标文件中添加对它的引用。但是,由于定义不可用,它实际上无法实例化函数模板。
编译AssetManager.cpp时,编译器看到函数模板的定义,但没有任何理由实例化它以获得任何T
要解决这个问题,只需养成立即在声明时定义模板的习惯-在您的情况下,将AssetManager :: GetAsset的定义移到其在AssetManager.h中的声明中即可。

-1

你不需要将源代码移动到头文件中。有一个快捷方式,可能不是最美观的,但它可以工作。 你可以在AssetManager.cpp中添加一个函数,基本上调用自身与GetAsset < Mesh* > (...)。然后该函数就被解决了。这是一种模板特化。使该“特殊”调用的输入参数什么也不做。

记得在cpp文件中包含Mesh.h而不是h文件,否则会出现嵌套包含导致混乱。

问候 马蒂亚斯


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接