Template specialization -- mutliple definition error -- C++

Started by
5 comments, last by Zahlman 15 years ago
It's all pretty much in the title there. I created a class with a few template methods, and I get a load of multiple definition errors from the linker.

#ifndef RESOURCES_H_INCLUDED
#define RESOURCES_H_INCLUDED

#include <map>
#include <string>
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <iostream>


/**
 * The Resources class holds some specialized types of resources, allowing for
 * easy maintaining.
 */
class Resources
{
private:

    std::map<std::string, SDL_Surface*> surfaces;
    std::map<std::string, Mix_Chunk*> sounds;

public:

    template<class T> void load(std::string, std::string);
    template<class T> void unload(std::string);

    template<class T> T get(std::string);

};


template<> void Resources::load<SDL_Surface*>(std::string file, std::string id)
{
    SDL_Surface* toLoad = SDL_LoadBMP(file.c_str());

    if (toLoad) {
        surfaces[id] = SDL_DisplayFormat(toLoad);
        SDL_FreeSurface(toLoad);
    } else {
        std::cout << "Error loading image \"" << file << "\" (" << id << ")" << std::endl;
    }
}

template<> void Resources::load<Mix_Chunk*>(std::string file, std::string id)
{
    sounds[id] = Mix_LoadWAV(file.c_str());

    if (!sounds[id]) {
        std::cout << "Error loading sound \"" << file << "\" (" << id << ")" << std::endl;
    }
}


template<> void Resources::unload<SDL_Surface*>(std::string id)
{
    SDL_FreeSurface(surfaces[id]);
    surfaces.erase(id);
}

template<> void Resources::unload<Mix_Chunk*>(std::string id)
{
    Mix_FreeChunk(sounds[id]);
    sounds.erase(id);
}


template<> SDL_Surface* Resources::get<SDL_Surface*>(std::string id)
{
    std::map<std::string, SDL_Surface*>::iterator iter = surfaces.find(id);
    return (iter == surfaces.end()) ? NULL : iter->second;
}

template<> Mix_Chunk* Resources::get<Mix_Chunk*>(std::string id)
{
    std::map<std::string, Mix_Chunk*>::iterator iter = sounds.find(id);
    return (iter == sounds.end()) ? NULL : iter->second;
}


#endif // RESOURCES_H_INCLUDED

obj/Debug/main.o: In function `SDL_Surface* Resources::get<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /usr/lib/gcc/i686-pc-linux-gnu/4.3.3/../../../../include/c++/4.3.3/new:105: multiple definition of `SDL_Surface* Resources::get<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:68: first defined here obj/Debug/main.o: In function `Mix_Chunk* Resources::get<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/resources.hpp:74: multiple definition of `Mix_Chunk* Resources::get<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:74: first defined here obj/Debug/main.o: In function `void Resources::unload<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/resources.hpp:61: multiple definition of `void Resources::unload<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:61: first defined here obj/Debug/main.o: In function `void Resources::unload<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/resources.hpp:55: multiple definition of `void Resources::unload<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:55: first defined here obj/Debug/main.o: In function `void Resources::load<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/resources.hpp:45: multiple definition of `void Resources::load<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:45: first defined here obj/Debug/main.o: In function `void Resources::load<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/resources.hpp:33: multiple definition of `void Resources::load<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:33: first defined here obj/Debug/states/mainmenu.o: In function `SDL_Surface* Resources::get<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/states/../resources.hpp:68: multiple definition of `SDL_Surface* Resources::get<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:68: first defined here obj/Debug/states/mainmenu.o: In function `Mix_Chunk* Resources::get<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/states/../resources.hpp:74: multiple definition of `Mix_Chunk* Resources::get<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:74: first defined here obj/Debug/states/mainmenu.o: In function `void Resources::unload<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/states/../resources.hpp:61: multiple definition of `void Resources::unload<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:61: first defined here obj/Debug/states/mainmenu.o: In function `void Resources::unload<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/states/../resources.hpp:55: multiple definition of `void Resources::unload<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:55: first defined here obj/Debug/states/mainmenu.o: In function `void Resources::load<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/states/../resources.hpp:45: multiple definition of `void Resources::load<Mix_Chunk*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:45: first defined here obj/Debug/states/mainmenu.o: In function `void Resources::load<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)': /home/rob/Development/C++/Tetris/states/../resources.hpp:33: multiple definition of `void Resources::load<SDL_Surface*>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)' obj/Debug/game.o:/home/rob/Development/C++/Tetris/resources.hpp:33: first defined here collect2: ld returned 1 exit status Process terminated with status 1 (0 minutes, 1 seconds) 24 errors, 0 warnings Thanks for any help.
Advertisement
I believe you still have to prototype your specializations in the class:

class Resources{private:    std::map<std::string, SDL_Surface*> surfaces;    std::map<std::string, Mix_Chunk*> sounds;public:    template<class T> void load(std::string, std::string);    template<class T> void unload(std::string);    template<class T> T get(std::string);    template<> void load<SDL_Surface*>(std::string,std::string);    template<> void unload<SDL_Surface*>(std::string);    template<> SDL_Surface *get<SDL_Surface*>(std::string);};// implementations as before


Seems to be the case with VS2008 anyway from a quick test - I don't know what the standard has to say.
#ifndef RESOURCES_H_INCLUDED#define RESOURCES_H_INCLUDED#include <map>#include <string>#include <SDL/SDL.h>#include <SDL/SDL_mixer.h>#include <iostream>/** * The Resources class holds some specialized types of resources, allowing for * easy maintaining. */class Resources{private:    std::map<std::string, SDL_Surface*> surfaces;    std::map<std::string, Mix_Chunk*> sounds;public:    template<class T> void load(std::string, std::string);    template<class T> void unload(std::string);    template<class T> T get(std::string);    template<> void load<SDL_Surface*>(std::string, std::string);    template<> void unload<SDL_Surface*>(std::string);    template<> SDL_Surface* get<SDL_Surface*>(std::string);    template<> void load<Mix_Chunk*>(std::string, std::string);    template<> void unload<Mix_Chunk*>(std::string);    template<> Mix_Chunk* get<Mix_Chunk*>(std::string);};template<> void Resources::load<SDL_Surface*>(std::string file, std::string id){    SDL_Surface* toLoad = SDL_LoadBMP(file.c_str());    if (toLoad) {        surfaces[id] = SDL_DisplayFormat(toLoad);        SDL_FreeSurface(toLoad);    } else {        std::cout << "Error loading image \"" << file << "\" (" << id << ")" << std::endl;    }}template<> void Resources::load<Mix_Chunk*>(std::string file, std::string id){    sounds[id] = Mix_LoadWAV(file.c_str());    if (!sounds[id]) {        std::cout << "Error loading sound \"" << file << "\" (" << id << ")" << std::endl;    }}template<> void Resources::unload<SDL_Surface*>(std::string id){    SDL_FreeSurface(surfaces[id]);    surfaces.erase(id);}template<> void Resources::unload<Mix_Chunk*>(std::string id){    Mix_FreeChunk(sounds[id]);    sounds.erase(id);}template<> SDL_Surface* Resources::get<SDL_Surface*>(std::string id){    std::map<std::string, SDL_Surface*>::iterator iter = surfaces.find(id);    return (iter == surfaces.end()) ? NULL : iter->second;}template<> Mix_Chunk* Resources::get<Mix_Chunk*>(std::string id){    std::map<std::string, Mix_Chunk*>::iterator iter = sounds.find(id);    return (iter == sounds.end()) ? NULL : iter->second;}#endif // RESOURCES_H_INCLUDED



-------------- Build: Debug in Tetris ---------------

Compiling: game.cpp
In file included from /home/rob/Development/C++/Tetris/game.hpp:13,
from /home/rob/Development/C++/Tetris/game.cpp:2:
/home/rob/Development/C++/Tetris/resources.hpp:29: error: explicit specialization in non-namespace scope ‘class Resources’
/home/rob/Development/C++/Tetris/resources.hpp:30: error: explicit specialization in non-namespace scope ‘class Resources’
/home/rob/Development/C++/Tetris/resources.hpp:31: error: explicit specialization in non-namespace scope ‘class Resources’
/home/rob/Development/C++/Tetris/resources.hpp:33: error: explicit specialization in non-namespace scope ‘class Resources’
/home/rob/Development/C++/Tetris/resources.hpp:34: error: explicit specialization in non-namespace scope ‘class Resources’
/home/rob/Development/C++/Tetris/resources.hpp:35: error: explicit specialization in non-namespace scope ‘class Resources’
Process terminated with status 1 (0 minutes, 0 seconds)
6 errors, 0 warnings


Doesn't seem to be the case with gnu gcc.
When I remove all the non-standard calls (and forward declare SDL_Surface and Mix_Chunk; I am at work right now and can't install third party libs), that header compiles fine.

What misses to give a better diagnostic is an actual sourcefile where you use class Resources, because due to lazy instantiation I could miss some errors.

Anyway, my guess is that your problem is the definition of non-inline member functions in the header file, and the ad-hoc solution would be to make your member functions inline, either by putting them into the class body, or by attributing them with inline.

Solution 2 would be to put the specialisations into an actual source file.


If that does not help, please provide a test case.


(not the solution, just the header in compilable form)
#include <map>#include <string>#include <iostream>class SDL_Surface;class Mix_Chunk;/** * The Resources class holds some specialized types of resources, allowing for * easy maintaining. */class Resources{private:    std::map<std::string, SDL_Surface*> surfaces;    std::map<std::string, Mix_Chunk*> sounds;public:    template<class T> void load(std::string, std::string);    template<class T> void unload(std::string);    template<class T> T get(std::string);};template<> void Resources::load<SDL_Surface*>(std::string file, std::string id){}template<> void Resources::load<Mix_Chunk*>(std::string file, std::string id){}template<> void Resources::unload<SDL_Surface*>(std::string id){    surfaces.erase(id);}template<> void Resources::unload<Mix_Chunk*>(std::string id){    sounds.erase(id);}template<> SDL_Surface* Resources::get<SDL_Surface*>(std::string id){    std::map<std::string, SDL_Surface*>::iterator iter = surfaces.find(id);    return (iter == surfaces.end()) ? NULL : iter->second;}template<> Mix_Chunk* Resources::get<Mix_Chunk*>(std::string id){    std::map<std::string, Mix_Chunk*>::iterator iter = sounds.find(id);    return (iter == sounds.end()) ? NULL : iter->second;}Resource res;
Perhaps another option would be not to use templates and specializations. After all the methods are only meant for two types and it is longer to type load<Image*> than load_image.
Indeed, moving the specializations to a seperate sourcefile was the solution. Thank you very much.


edit After some rethinking, I've come to the conclusion my approach really beats the purpose of templates here, because I end up redefining each function for each resource type anyway. I've changed it to Resources::get_image() and the likes.

[Edited by - c4c0d3m0n on April 8, 2009 2:10:19 PM]
Only specialize what needs to be specialized. Refactor the common stuff. :)

// EDIT: Fixed, with stubs provided to demonstrate that it compiles.#ifndef RESOURCES_H_INCLUDED#define RESOURCES_H_INCLUDED#include <map>#include <string>#include <stdexcept>class SDL_Surface {};class Mix_Chunk {};SDL_Surface* SDL_LoadBMP(const char* const name) { return new SDL_Surface(); }SDL_Surface* SDL_DisplayFormat(SDL_Surface* old) { return new SDL_Surface(*old); }void SDL_FreeSurface(SDL_Surface* dead) { delete dead; }Mix_Chunk* Mix_LoadWAV(const char* const name) { return new Mix_Chunk(); }void Mix_FreeChunk(Mix_Chunk* dead) { delete dead; }//#include <SDL/SDL.h>//#include <SDL/SDL_mixer.h>#include <iostream>template <typename T>struct helpers {  typedef T* (*loader)(const char* const);  static loader load;  typedef void (*unloader)(T*);  static unloader unload;  static const char* const resource_description;};SDL_Surface* load_optimized(const char* const filename) {  SDL_Surface* toLoad = SDL_LoadBMP(filename);  if (toLoad) {    SDL_Surface* optimized = SDL_DisplayFormat(toLoad);    SDL_FreeSurface(toLoad);    toLoad = optimized;  }  return toLoad;}template<> helpers<SDL_Surface>::loader helpers<SDL_Surface>::load = load_optimized;template<> helpers<SDL_Surface>::unloader helpers<SDL_Surface>::unload = SDL_FreeSurface;template<> const char* const helpers<SDL_Surface>::resource_description = "image";template<> helpers<Mix_Chunk>::loader helpers<Mix_Chunk>::load = Mix_LoadWAV;template<> helpers<Mix_Chunk>::unloader helpers<Mix_Chunk>::unload = Mix_FreeChunk;template<> const char* const helpers<Mix_Chunk>::resource_description = "sound";template <typename T>class ResourceRegistry {  // You'll have to make a separate one for each type now. Oh Well.  std::map<std::string, T*> resources;  public:  void load(std::string filename, std::string id) {    T* resource = helpers<T>::load(filename.c_str());    if (resource) {      resources[id] = resource;    } else {      // Don't print the message here; throw an exception.      // In general, let callers decide how to report errors. Otherwise you      // lose huge amounts of flexibility.      throw std::runtime_error(        std::string("Could not load ") + helpers<T>::resource_description +         " \"" + filename + "\" (" + id + ")"      );    }  }  void unload(const std::string& id) {    helpers<T>::unload(resources[id]);    resources.erase(id);  }  T* get(const std::string& id) {    typename std::map<std::string, T*>::iterator iter = resources.find(id);    return (iter == resources.end()) ? 0 : iter->second;  }};#endifint main() {	ResourceRegistry<Mix_Chunk> mc;	ResourceRegistry<SDL_Surface> ss;	mc.load("foo", "bar");	mc.get("bar");	mc.unload("bar");	ss.load("foo", "bar");	ss.get("bar");	ss.unload("bar");}


This approach also makes it easier to do something similar for other resources in the future, if you find that there are others that require similar management.

[Edited by - Zahlman on April 8, 2009 3:59:01 PM]

This topic is closed to new replies.

Advertisement