Reputation: 9681
I've been playing with std::map
lately and came up with a grand :D design to create a priority map - a map which contains various modes that are grouped based on their priorities.
I have the following class structure:
Mode
|
|----ModeSleep
|----ModeFactorial
where Mode
is:
class Mode
{
std::string name; ///< Mode's name
int priority; ///< Mode's priority used for storing the Mode in a specific priority group in the priority map. Default: 0
public:
Mode();
///
/// \brief Mode
/// \param name Mode's name
/// \param priority Mode's priority used for storing the Mode in a specific priority group in the priority map. Default: 0
///
Mode(const std::string &name, const int priority=0);
virtual ~Mode();
std::string getName() const;
void setName(const std::string &value);
int getPriority() const;
void setPriority(int value);
///
/// \brief run is the part of a Mode which is executed by the ModeExecutor
///
virtual void run() = 0;
};
On the other hand I have another class which uses Mode
called PriorityMap
with the following class definition:
class PriorityMap
{
typedef std::pair<int, Mode *> ModeEntry;
typedef std::map<int, Mode *> PriorityGroup;
typedef PriorityGroup* PriorityGroup_Ptr;
typedef std::map<int, PriorityGroup_Ptr> Priority;
typedef Priority* Priority_Ptr;
Priority_Ptr priorities;
bool _insert(Mode *mode);
Mode *_find(const std::string &name);
public:
PriorityMap();
~PriorityMap();
void print();
void insert(Mode *mode);
template<class T> T *find(const std::string &name);
};
Below you can see an example of how the objects are initialized and called:
int main ()
{
PriorityMap *priorities = new PriorityMap();
ModeSleep *m1 = new ModeSleep("Sleep10", 0, 10);
priorities->insert(m1);
ModeSleep *m2 = new ModeSleep("Sleep5", 0, 5);
priorities->insert(m2);
ModeFactorial *m3 = new ModeFactorial("Factorial20", 1, 20);
priorities->insert(m3);
priorities->print();
// Example for a correct match (both name and type) - ERROR!!!
ModeSleep *foundM2 = priorities->template find<ModeSleep>("Sleep5");
if(foundM2)
std::cout << "Found mode \"" << foundM2->getName() << "\" has time interval set to " << foundM2->getMilliseconds() << "ms" << std::endl;
// Example for correct name match but incorrect type - ERROR!!!
ModeSleep *foundM1 = priorities->template find<ModeSleep>("Factorial20");
if(foundM1)
std::cout << "Found mode \"" << foundM1->getName() << "\" has time interval set to " << foundM1->getMilliseconds() << "ms" << std::endl;
delete priorities;
return 0;
}
At first I didn't have any template-stuff for my find()
(once I did I moved the original find()
as a private _find()
called inside the template
version of find()
). My initial design (now _find()
) was:
Mode *PriorityMap::_find(const std::string &name)
{
for(const auto& priorityGroup : *priorities)
for(auto& modeEntry : *(priorityGroup.second))
if(!name.compare((modeEntry.second->getName())))
return modeEntry.second;
return nullptr;
}
After running find()
a couple of times I faced the problem that I had to manually downcast the returned pointer to the respective derivation of Mode
(in my case just ModeSleep
and ModeFactorial
). So I decided that adding a template-functionality to that function and also adding some feedback when calling it would be useful:
template<class T>
T *PriorityMap::find(const std::string &name)
{
Mode *foundMode = _find(name);
if(foundMode) {
T *foundModeCast = dynamic_cast<T *>(foundMode);
if(foundModeCast) {
std::cout << "Found mode \"" << foundModeCast->getName() << "\"" << std::endl;
return foundModeCast;
}
else {
std::cout << "Found mode \"" << foundMode->getName() << "\" however specified type is invalid! Returning NULL" << std::endl;
return nullptr;
}
}
}
As you can see according to my definition a found mode inside my priority map is based on two factors:
name
is a matchI have a problem with the calling of my find()
though and I my build breaks at the first usage of it with the following error:
In function `main':
undefined reference to `ModeSleep *PriorityMap::find<ModeSleep>(std::string const&);'
I haven't done much of template member functions and would appreciate some feedback on this. If you need more information do tell and will provide it.
PS: For those of you wondering about the way the way modes are found based on their names - I'm actually going to change my find()
to return a vector of references since names are not unique in my case and I can have modes with the same name at different places in my priority map. Right now find()
returns the first match but should suffice for the purpose of this post.
Upvotes: 1
Views: 1267
Reputation: 10316
You need to define the function PriorityMap::find
in your header file rather than your cpp file.
The problem is that with template functions, no instantiations are created in a given compilation unit unless the function instantiation is actually used in the said unit. The compilation unit in which you define your function is not the same one in which you actually use it, so in your case no instantiations are actually created. Later when it comes to linking the compilation unit(s) in which the function is used no definition is therefore found, so you get a linker error.
If you want to avoid defining the function in the header file then you can explicitly instantiate it in the cpp file in which it is defined. For example:
template void PriorityMap::find(MyClass);
The downside here is that this cpp file will have to know about all the types that will ever have to be used with PriorityMap::find
Upvotes: 3