Error LINK2005 already defined in Main.obj

Jens picture Jens · Mar 22, 2015 · Viewed 6.9k times · Source

i have this problem where i cannot compile if i include a certain header.

The code for the header:

//#include "tinyxml2.h"

#pragma once

#include "Camera.h"
#include "Controller.h"
#include "Lights.h"
#include "Mesh.h"

namespace ActorFactory {

    //using namespace tinyxml2;

    template<class T>  
    Actor* createInstance() { 
        return new T; 
    }

    typedef std::map<std::string, Actor*(*)()> class_map;
    class_map test = {
        { "Camera", &createInstance<Camera> }
        //{ "Mesh", &createInstance<Mesh> }
    };
}

The full error message from visual studio:

1>------ Build started: Project: ogl_inf251_ca2, Configuration: Debug Win32     ------
1>  Main.cpp
1>d:\development\inf251\ogl_inf251_ca2\model_obj.h(26): warning C4005:     '_CRT_SECURE_NO_WARNINGS' : macro redefinition
1>          command-line arguments :  see previous definition of     '_CRT_SECURE_NO_WARNINGS'
1>  Generating Code...
1>  Compiling...
1>  Scene.cpp
1>d:\development\inf251\ogl_inf251_ca2\model_obj.h(26): warning C4005:     '_CRT_SECURE_NO_WARNINGS' : macro redefinition
1>          command-line arguments :  see previous definition of     '_CRT_SECURE_NO_WARNINGS'
1>  Generating Code...
1>Scene.obj : error LNK2005: "class std::map<class     std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>     >,class Actor * (__cdecl*)(void),struct std::less<class     std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>     > >,class std::allocator<struct std::pair<class std::basic_string<char,struct     std::char_traits<char>,class std::allocator<char> > const ,class Actor *     (__cdecl*)(void)> > > ActorFactory::test" (?test@ActorFactory@@3V?$map@V?    $basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@P6APAVActor@@XZU?    $less@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@V?    $allocator@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@std@@V?    $allocator@D@2@@std@@P6APAVActor@@XZ@std@@@2@@std@@A) already defined in     Main.obj
1>D:\development\inf251\Debug\ogl_inf251_ca2.exe : fatal error LNK1169: one     or more multiply defined symbols found
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

The error message is really long unfortunately. I have no idea how to fix this or even whats wrong. Also is there a better way to instantiate classes by name?

Thanks for any help in advance :)

Answer

Christian Hackl picture Christian Hackl · Mar 22, 2015

Your test object violates the one-definition rule.

The #pragma once keeps the definition from appearing multiple times in the same compilation unit (read: the same *.cpp file), but not from appearing within different compilation units (read: different *.cpp files).

One way to fix this problem is to convert the object into a static local object within a function and make it accessible via a reference:

header.h:

// ...

namespace ActorFactory {

    // ...

    typedef std::map<std::string, Actor*(*)()> class_map;
    class_map& test();
}

test.cpp:

#include "header.h"

namespace ActorFactory {

    // ...

    class_map& test() {
        static class_map test = {
            { "Camera", &createInstance<Camera> }
            { "Mesh", &createInstance<Mesh> };
        return test;
    }
}

This is also a fail-safe guarantee against the so-called static initialization order fiasco, and gives you lazy initialisation (not that it matters much with this tiny object).

You can make the whole thing even safer (in the face of order-of-destruction issues) by allocating the object with new and never deleting it, but the function should still return a reference:

namespace ActorFactory {

    // ...

    class_map& test() {
        static class_map* test = new class_map {
            { "Camera", &createInstance<Camera> }
            { "Mesh", &createInstance<Mesh> };
        return *test;
    }
}

Read the FAQs I linked to above to get all the details.

In any case, client code then simply accesses the object via test() instead of test.