I have the following class:
class Karen
{
public:
Karen(void);
~Karen(void);
void complain(std::string level);
private:
void debug(void) const;
void info(void) const;
void warning(void) const;
void error(void) const;
};
The complain function receives a string that can contain the words debug, info, warning or error, and it has to call the appropriate function without using a forest of if/elseif/else, using instead pointers to member functions. The prototype of complain is given to me. I am new to pointers to member functions and I am not sure how to manage this. One of my attempts is this:
void Karen::complain(std::string level)
{
std::string *p = &level;
void (Karen::*f)(void) const;
(this->*(*p))();
}
The syntax of the last line is incorrect, but I am trying to do (this->*(content of pointer p))() and I don't know how to write this. Can someone help me?
Edit I am only allowed to use C++98
Syntax to call a member function via member function pointer is
(this->*memf)();
You cannot magically turn the string into a member function pointer. Sloppy speaking, names of functions do not exist at runtime. If you want such mapping you need to provide it yourself. No way around that. What you can avoid is the "forest of if-else" by using a std::unordered_map:
#include <unordered_map>
#include <string>
#include <iostream>
class Karen
{
public:
void complain(std::string level) {
static const std::unordered_map<std::string, void(Karen::*)() const> m{
{"debug",&Karen::debug},
{"info",&Karen::info},
{"warning",&Karen::warning},
{"error",&Karen::error}
};
auto it = m.find(level);
if (it == m.end()) return;
(this->*(it->second))();
}
private:
void debug(void) const { std::cout << "debug\n"; }
void info(void) const { std::cout << "info\n"; }
void warning(void) const { std::cout << "warning\n"; }
void error(void) const { std::cout << "error\n"; }
};
int main() {
Karen k;
k.complain("info");
}
Live Demo
As mentioned in comments, you could use an enum in place of the string. When possible you should use the help of the compiler, which can diagnose a typo in an enum but not in a string. Alternatively you could directly pass a member function pointer to complain. Then implementation of complain would be trivial, no branching needed. Though this would require the methods to be public and the caller would have to deal with member function pointers.
If you are not allowed to use C++11 or newer you should have a serious talk with your teacher. Soon C++20 will be the de facto standard and things have changed quite a lot. I am not fluent in C++98 anymore, so here is just a quick fix of the above to get it working somehow. You cannot use std::unordered_map but there is std::map and initialization of the map is rather cumbersome:
#include <map>
#include <string>
#include <iostream>
class Karen
{
typedef void(Karen::*memf_t)() const;
typedef std::map<std::string,void(Karen::*)() const> map_t;
public:
void complain(std::string level) {
map_t::const_iterator it = get_map().find(level);
if (it == get_map().end()) return;
(this->*(it->second))();
}
private:
const map_t& get_map(){
static const map_t m = construct_map();
return m;
}
const map_t construct_map() {
map_t m;
m["debug"] = &Karen::debug;
m["info"] = &Karen::info;
m["warning"] = &Karen::warning;
m["error"] = &Karen::error;
return m;
}
void debug(void) const { std::cout << "debug\n"; }
void info(void) const { std::cout << "info\n"; }
void warning(void) const { std::cout << "warning\n"; }
void error(void) const { std::cout << "error\n"; }
};
int main() {
Karen k;
k.complain("info");
}
Live Demo
Let us start with the required includes.
#include <cassert>
#include <iostream>
#include <map>
Providing the log level as a string, can lead to errors, since the compiler cannot check for typos. Hence an enum::class would be a better choice for determining the log level.
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR
};
C++ does not offer a way to obtain a function pointer given a string. After compiling and linking the function names all have been replaced by their appropriate addresses in memory. Hence, we first need to store the function pointers, in a way that allows us to look them up as needed. For this purpose you can use a static class attribute, and store the function pointers in a std::map.
class Logger
{
public:
Logger();
~Logger();
void complain(LogLevel level);
private:
void debug() const;
void info() const;
void warning() const;
void error() const;
using HandlerMap = std::map<LogLevel, void (Logger::*)(void) const>;
static HandlerMap handlers;
};
Logger::HandlerMap Logger::handlers{
{LogLevel::DEBUG, &Logger::debug},
{LogLevel::INFO, &Logger::info},
{LogLevel::WARNING, &Logger::warning},
{LogLevel::ERROR, &Logger::error}
};
The complain method then just needs to look up the correct function pointer and call the method.
void Logger::complain(LogLevel level) {
assert(handlers.find(level) != handlers.end());
(this->*handlers[level])();
}
The remaining functions look as follows.
Logger::Logger() {}
Logger::~Logger() {}
void Logger::debug() const { std::cout << "debug" << std::endl; }
void Logger::info() const { std::cout << "info" << std::endl; }
void Logger::warning() const { std::cout << "warning" << std::endl; }
void Logger::error() const { std::cout << "error" << std::endl; }
int main(int argc, char* argv[]) {
Logger k;
k.complain(LogLevel::DEBUG);
k.complain(LogLevel::INFO);
k.complain(LogLevel::WARNING);
k.complain(LogLevel::ERROR);
}
Note, if you insist on using strings, you can replace LogLevel by std::string and LogLevel::<member> by the corresponding string.
The same can be achived using C++98. However, you will need a bit more bootstrapping.
#include <cassert>
#include <iostream>
#include <map>
enum LogLevel {
LL_DEBUG,
LL_INFO,
LL_WARNING,
LL_ERROR
};
class Logger
{
public:
Logger();
~Logger();
void complain(LogLevel level);
typedef std::map<LogLevel, void (Logger::*)() const> HandlerMap;
friend struct LoggerInit;
private:
void debug() const;
void info() const;
void warning() const;
void error() const;
static HandlerMap handlers;
};
Logger::HandlerMap Logger::handlers = Logger::HandlerMap();
struct LoggerInit {
LoggerInit() {
Logger::handlers[LL_DEBUG] = &Logger::debug;
Logger::handlers[LL_INFO] = &Logger::info;
Logger::handlers[LL_WARNING] = &Logger::warning;
Logger::handlers[LL_ERROR] = &Logger::error;
}
} logger_init;
void Logger::complain(LogLevel level) {
assert(handlers.find(level) != handlers.end());
(this->*handlers[level])();
}
Logger::Logger() {}
Logger::~Logger() {}
void Logger::debug() const { std::cout << "debug" << std::endl; }
void Logger::info() const { std::cout << "info" << std::endl; }
void Logger::warning() const { std::cout << "warning" << std::endl; }
void Logger::error() const { std::cout << "error" << std::endl; }
int main(int argc, char* argv[]) {
Logger k;
k.complain(LL_DEBUG);
k.complain(LL_INFO);
k.complain(LL_WARNING);
k.complain(LL_ERROR);
}
Related
Today I'm working on a singleton test case in c++.
The singleton is working fine but I would like to instantiate the static object when the user try to access a member of it, so if the variable isn't created when we try to access a member of it, it will not crash instead it will simply generate my singleton.
Here's my class.h:
class PDG : public EmployeRH
{
public:
static void Instantiate(std::string nom, std::string prenom);
// Current manual instantiation version of the singleton
PDG* operator->();
// This is the line I just added to overload "->" operator ... But it seems it's never called.
void SePresenter();
static PDG* _instance;
private:
PDG();
~PDG();
PDG(std::string nom, std::string prenom);
int _budget;
};
Methods.cpp
PDG* PDG::_instance=NULL;
PDG::PDG()
{
}
PDG::~PDG()
{
}
PDG::PDG(std::string a_nom, std::string a_prenom):EmployeRH(a_nom,a_prenom)
{
_budget = 100000;
}
void PDG::Instantiate(std::string a_nom, std::string a_prenom)
{
cout << "instantiation pdg" << endl;
if (_instance == NULL)
{
_instance = new PDG(a_nom,a_prenom);
}
}
PDG* PDG::operator->()
{
PDG::Instantiate("Unknown", "Unknown");
return _instance;
}
void PDG::SePresenter()
{
cout << _nom << " " << _prenom << endl;
}
main.cpp
void main()
{
PDG::_instance->SePresenter();
system("pause");
}
The thing is, it goes directly into "SePresenter()" and not into my overloaded operator "->".
If anyone could help it would be greatfull.
Thanks,
Impact
PDG::_instance is a pointer to PDG so -> simply dereferences the pointer and you can't override the behaviour. To override the -> operator you must call it on the class directly not on a pointer: (*PDG::_instance)->SePresenter(). To preserve your desired syntax and to remove the undefined behaviour from dereferencing the null pointer you can change PDG::_instance into a structure which holds your instance pointer.
#include <string>
#include <iostream>
using namespace std;
struct EmployeRH {
EmployeRH() {}
EmployeRH(std::string nom, std::string prenom) {}
std::string _nom;
std::string _prenom;
};
class PDG : public EmployeRH {
public:
static PDG* Instantiate(std::string nom, std::string prenom);
// Current manual instantiation version of the singleton
void SePresenter();
static struct Instance {
PDG* operator->()
{
return PDG::Instantiate("Unknown", "Unknown");
}
} _instance;
private:
PDG();
~PDG();
PDG(std::string nom, std::string prenom);
int _budget;
};
PDG::Instance PDG::_instance;
PDG::PDG()
{
}
PDG::~PDG()
{
}
PDG::PDG(std::string a_nom, std::string a_prenom)
: EmployeRH(a_nom, a_prenom)
{
_budget = 100000;
}
PDG* PDG::Instantiate(std::string a_nom, std::string a_prenom)
{
static PDG instance(a_nom, a_prenom);
cout << "instantiation pdg" << endl;
return &instance;
}
void PDG::SePresenter()
{
cout << _nom << " " << _prenom << endl;
}
int main()
{
PDG::_instance->SePresenter();
return 0;
}
I've also changed your singleton to use a function static which makes your code thread safe.
The following "Event" code snippet shows the "pure virtual function call" error. However, as mentioned in the title, it happens only when deploying on DEBUG. What makes me curious is why it works flawlessly on RELEASE and why it does even crash (on DEBUG).
Alternatively, you can see the snippet here.
#include <list>
#include <iostream>
#include <algorithm>
// use base class to resolve the problem of how to put into collection objects of different types
template <typename TPropertyType>
struct PropertyChangedDelegateBase
{
virtual ~PropertyChangedDelegateBase(){};
virtual void operator()(const TPropertyType& t) = 0;
};
template <typename THandlerOwner, typename TPropertyType>
struct PropertyChangedDelegate : public PropertyChangedDelegateBase<TPropertyType>
{
THandlerOwner* pHandlerOwner_;
typedef void (THandlerOwner::*TPropertyChangeHandler)(const TPropertyType&);
TPropertyChangeHandler handler_;
public:
PropertyChangedDelegate(THandlerOwner* pHandlerOwner, TPropertyChangeHandler handler) :
pHandlerOwner_(pHandlerOwner), handler_(handler){}
void operator()(const TPropertyType& t)
{
(pHandlerOwner_->*handler_)(t);
}
};
template<typename TPropertyType>
class PropertyChangedEvent
{
public:
virtual ~PropertyChangedEvent(){};
void add(PropertyChangedDelegateBase<TPropertyType>* const d)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if(it != observers_.end())
throw std::runtime_error("Observer already registered");
observers_.push_back(d);
}
void remove(PropertyChangedDelegateBase<TPropertyType>* const d)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if(it != observers_.end())
observers_.remove(d);
}
// notify
void operator()(const TPropertyType& newValue)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = observers_.begin();
for(; it != observers_.end(); ++it)
{
(*it)->operator()(newValue);
}
}
protected:
std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_;
};
class PropertyOwner
{
int property1_;
float property2_;
public:
PropertyChangedEvent<int> property1ChangedEvent;
PropertyChangedEvent<float> property2ChangedEvent;
PropertyOwner() :
property1_(0),
property2_(0.0f)
{}
int property1() const {return property1_;}
void property1(int n)
{
if(property1_ != n)
{
property1_ = n;
property1ChangedEvent(n);
}
}
float property2() const {return property2_;}
void property2(float n)
{
if(property2_ != n)
{
property2_ = n;
property2ChangedEvent(n);
}
}
};
struct PropertyObserver
{
void OnPropertyChanged(const int& newValue)
{
std::cout << "PropertyObserver::OnPropertyChanged() -> new value is: " << newValue << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
PropertyOwner propertyOwner;
PropertyObserver propertyObserver;
// register observers
PropertyChangedDelegate<PropertyObserver, int> delegate(&propertyObserver, &PropertyObserver::OnPropertyChanged);
propertyOwner.property1ChangedEvent.add(&delegate); // Ok!
propertyOwner.property1ChangedEvent.add(&PropertyChangedDelegate<PropertyObserver, int>(&propertyObserver, &PropertyObserver::OnPropertyChanged)); // Error: Virtual pure function call (Debug only)
propertyOwner.property1(1);
return getchar();
}
I would assume that the error is misnomer and that the problem is more likely to do with the scope that the second delegate lives. Plus declaring it outside is easier to read.
Passing around an object created on the stack rather than the heap by reference is usually a bad idea. Once the item declaration is out of scope the object is usually forgotten about.
The general issue is that you are binding to a temporary that gets destroyed and thus has an empty vtable and of course it generates a pure virtual call when invoked on the change of the property. If you add a dtor for the base class this is quite easy to observe:
#include <list>
#include <iostream>
#include <algorithm>
// use base class to resolve the problem of how to put into collection objects of different types
template <typename TPropertyType>
struct PropertyChangedDelegateBase
{
virtual ~PropertyChangedDelegateBase(){};
virtual void operator()(const TPropertyType& t) = 0;
};
template <typename THandlerOwner, typename TPropertyType>
struct PropertyChangedDelegate : public PropertyChangedDelegateBase<TPropertyType>
{
THandlerOwner* pHandlerOwner_;
typedef void (THandlerOwner::*TPropertyChangeHandler)(const TPropertyType&);
TPropertyChangeHandler handler_;
public:
PropertyChangedDelegate(THandlerOwner* pHandlerOwner, TPropertyChangeHandler handler) :
pHandlerOwner_(pHandlerOwner), handler_(handler)
{
std::cout << "0x" << std::hex << this << " created!" << std::endl;
}
void operator()(const TPropertyType& t)
{
(pHandlerOwner_->*handler_)(t);
}
~PropertyChangedDelegate()
{
std::cout << "0x" << std::hex << this << " destroyed!" << std::endl;
}
};
template<typename TPropertyType>
class PropertyChangedEvent
{
public:
virtual ~PropertyChangedEvent(){};
void add(PropertyChangedDelegateBase<TPropertyType>* const d)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if (it != observers_.end())
throw std::runtime_error("Observer already registered");
observers_.push_back(d);
}
void remove(PropertyChangedDelegateBase<TPropertyType>* const d)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if (it != observers_.end())
observers_.remove(d);
}
// notify
void operator()(const TPropertyType& newValue)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = observers_.begin();
for (; it != observers_.end(); ++it)
{
std::cout << "Invoking 0x" << std::hex << *it << std::endl;
(*it)->operator()(newValue);
}
}
protected:
std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_;
};
class PropertyOwner
{
int property1_;
float property2_;
public:
PropertyChangedEvent<int> property1ChangedEvent;
PropertyChangedEvent<float> property2ChangedEvent;
PropertyOwner() :
property1_(0),
property2_(0.0f)
{}
int property1() const { return property1_; }
void property1(int n)
{
if (property1_ != n)
{
property1_ = n;
property1ChangedEvent(n);
}
}
float property2() const { return property2_; }
void property2(float n)
{
if (property2_ != n)
{
property2_ = n;
property2ChangedEvent(n);
}
}
};
struct PropertyObserver
{
void OnPropertyChanged(const int& newValue)
{
std::cout << "PropertyObserver::OnPropertyChanged() -> new value is: " << newValue << std::endl;
}
};
int main(int argc, char* argv[])
{
PropertyOwner propertyOwner;
PropertyObserver propertyObserver;
// register observers
PropertyChangedDelegate<PropertyObserver, int> delegate(&propertyObserver, &PropertyObserver::OnPropertyChanged);
propertyOwner.property1ChangedEvent.add(&delegate); // Ok!
propertyOwner.property1ChangedEvent.add(&PropertyChangedDelegate<PropertyObserver, int>(&propertyObserver, &PropertyObserver::OnPropertyChanged)); // Error: Virtual pure function call (Debug only)
propertyOwner.property1(1);
return getchar();
}
Basically you are just running into undefined behavior - the object is destroyed in both cases, but in Release the vtable is not destroyed so you get by.
This:
propertyOwner.property1ChangedEvent.add(
&PropertyChangedDelegate<PropertyObserver, int>(
&propertyObserver,
&PropertyObserver::OnPropertyChanged)
);
You are capturing a pointer to a temporary object PropertyChangedDelegate<PropertyObserver, int>. Pointer to this object becomes invalid as soon as function call is over and temporary is destroyed. Dereferencing this pointer is undefined behavior.
In your program, memory ownership relations are critical and you should think them through carefully.
You need to ensure that all your pointers outlive objects that rely on them, either manually:
PropertyChangedDelegate<PropertyObserver, int> delegate2 = {
&propertyObserver,
&PropertyObserver::OnPropertyChanged
};
propertyOwner.property1ChangedEvent.add(&delegate2);
or by using smart pointers (std::unique_ptr<>, std::shared_ptr<>).
Another bug:
C++11 compliant compier should not allow you doing this:
std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_;
The error I got with Visual Studio 2015 is:
The C++ Standard forbids containers of const elements because allocator is ill-formed.`
See: Does C++11 allow vector<const T>?
Bonus:
Your C++ style looks quite a bit obsolete.
You might want to try automatic type deduction:
for(auto it = observers_.begin(); it != observers_.end(); ++it)
{
(*it)->operator()(newValue);
}
or, better, ranged for loops:
for(auto observer : observers)
{
observer(newValue);
}
You might want to take a look to:
The Definitive C++ Book Guide and List
C++ Core Guidelines
I'm trying to access one of my pointers within my class, however I can't seem to even get VS to acknowledge the pointer is there.
private:
ForwardTo* forward;
and here's how I grab it from the class
ForwardTo& Persons::getForwardTo() const
{
return *forward;
}
The ForwardTo type is an inherited class that typically reads:
class ForwardToTwo : public ForwardTo
{
public:
ForwardToTwo(unsigned int strategy);
virtual std::vector<std::string> forwardMessage(Persons& person, Message& message);
unsigned int getStrategy() const { return strategy;};
private:
unsigned int strategy;
};
and finally, here's how I'm trying to access the pointer
listOfPersons.at(i).getForwardTo()->forwardMessage(listOfPersons.at(i), tempMessage);
This style of access worked previously for my OTHER pointer in my class that acts exactly this same as this one.
While typing the line to access the pointer out in VS, intelliSense picks up all the functions up to getForwardTo(), and after that, the dot operator/arrow operator don't bring up any access to functions.
Thanks again for all help.
(edit, I'm aware the function fordwardMessage() function will return a vector, I was just typing in the VS until intelliSense failed to detect the accessible functions)
(edit 2, I've tried both the . operator and the -> operator, yet neither allow intelliSense to detect any functions.)
(edit 3, additional code:
Here is my Persons Class Header:
#ifndef PERSONS_HPP
#define PERSONS_HPP
#include <string>
#include <vector>
#include <list>
#include <map>
#include "Message.hpp"
#include "TypeOne.hpp"
#include "TypeTwo.hpp"
#include "TypeThree.hpp"
#include "TypeFour.hpp"
#include "TypeFive.hpp"
class ForwardTo;
class ForwardToOne;
class ForwardToTwo;
class ForwardToThree;
class Persons
{
public:
Persons();
~Persons();
void setEmailAddress(std::string email);
std::string getEmailAddress() const;
const std::vector<std::string>& getContactList() const;
void addMessageSeen(Message message);
void addMessageContent(MessageContent mc);
void addInboxMessage(Message message);
void addContact(std::string contact);
void viewContact(const std::vector<std::string>& contacts);
void bumpContact();
void setMessageTypeOne();
void setMessageTypeTwo(unsigned int type);
void setMessageTypeThree(unsigned int quality);
void setMessageTypeFour(unsigned int type, unsigned int quality);
void setMessageTypeFive();
void setForwardTypeOne(unsigned int strategy);
void setForwardTypeTwo(unsigned int strategy);
void setForwardTypeThree(unsigned int strategy);
void printPersonsObj();
std::list<Message> getInbox() const;
MessageType& getForwardWhen() const;
ForwardTo& getForwardTo() const;
private:
std::map<MessageContent, unsigned int> messageList;
std::list<Message> inbox;
std::vector<std::string> contactList;
std::string emailAddress;
ForwardTo* forward;
MessageType* forwardWhen;
};
And here is my Persons.cpp file is:
#include "Persons.hpp"
#include "ForwardToOne.hpp"
#include "ForwardToTwo.hpp"
#include "ForwardToThree.hpp"
#include <iostream>
Persons::Persons()
:emailAddress(""), contactList(), inbox(), messageList()
{
}
Persons::~Persons()
{
//delete forwardWhen;
//delete forwardTo;
}
void Persons::addMessageContent(MessageContent mc)
{
//messageSeen.insert(mc);
}
void Persons::setEmailAddress(std::string email)
{
emailAddress = email;
}
std::string Persons::getEmailAddress() const
{
return emailAddress;
}
void Persons::addContact(std::string contact)
{
contactList.push_back(contact);
}
void Persons::addInboxMessage(Message message)
{
inbox.push_back(message);
}
void Persons::viewContact(const std::vector<std::string>& contacts)
{
for(auto i = contacts.begin(); i != contacts.end(); i ++)
{
std::cout << *i << std::endl;;
}
}
void Persons::setMessageTypeOne()
{
MessageType* forwardWhen = new TypeOne();
}
void Persons::setMessageTypeTwo(unsigned int type)
{
MessageType* forwardWhen = new TypeTwo(type);
}
void Persons::setMessageTypeThree(unsigned int quality)
{
MessageType* forwardWhen = new TypeThree(quality);
}
void Persons::setMessageTypeFour(unsigned int type, unsigned int quality)
{
MessageType* forwardWhen = new TypeFour(type, quality);
}
void Persons::setMessageTypeFive()
{
MessageType* forwardWhen = new TypeFive();
}
void Persons::setForwardTypeOne(unsigned int strategy)
{
ForwardTo* forward = new ForwardToOne(strategy);
}
void Persons::setForwardTypeTwo(unsigned int strategy)
{
ForwardTo* forward = new ForwardToTwo(strategy);
}
void Persons::setForwardTypeThree(unsigned int strategy)
{
ForwardTo* forward = new ForwardToThree(strategy);
}
const std::vector<std::string>& Persons::getContactList() const
{
return contactList;
}
void Persons::bumpContact()
{
std::vector<std::string> tempList = getContactList();
std::string tempContact = tempList.at(0);
for(unsigned int i = 0; i <= tempList.size(); i ++)
{
if(i == tempList.size())
tempList.at(--i) = tempContact;
else
tempList.at(--i) = tempList.at(i);
}
}
void Persons::addMessageSeen(Message message)
{
messageList[*message.getMessageContent()] = message.getMessageContent()->getUniqueID();
}
void Persons::printPersonsObj()
{
std::cout << "PERSONS OBJECT!" << std::endl;
std::cout << "E-mail Address: " << emailAddress << std::endl;
std::cout << std::endl;
}
std::list<Message> Persons::getInbox() const
{
return inbox;
}
MessageType& Persons::getForwardWhen() const
{
return *forwardWhen;
}
ForwardTo& Persons::getForwardTo() const
{
return *forward;
}
Incomplete type normally means that at the point at which you are trying to use getForwardTo, you have not fully declared the ForwardTo class. Indeed in your persons.hop there is only a forward declaration.
Make sure you have included the header that fully declares ForwardTo in the file containing the call site.
getForwardTo() returns a T&, just use the . to access forwardMessage()
I have a templated class Parameter which can (or must) be specialized.
I want to put all my parameters in a container.
How to do this if my parameters are instanciated with different types?
In the class Container, I would like to have a vector<Parameter*> from different types (int, double, ...) or something equivalent which seems to not possible.
If the Parameter class is derived from a base class, then The Container can declare the vect as vector<Base*>. But in this case, we can do nothing specific in Container::foo.
Below is my source example. One of my parameters is a QString which is not compatible with ostream.
Thanks for your comments.
#include <QString>
#include <vector>
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
#define P(a) cout << #a << ":" << a << endl
/*
class Base {
};
*/
template<typename T> class Parameter /*: public Base */ {
private:
T val;
public:
void setVal(const T &val) {
this->val = val;
}
const T &getVal() {
return val;
}
string getFoo() {
stringstream s;
s << val;
return s.str();
}
};
template<>
string Parameter<QString>::getFoo() {
stringstream s;
s << val.toStdString();
return s.str();
}
class Container {
public:
void push_back(Parameter *base) {
vect.push_back(base);
}
void foo() {
/* do something with the parameters */
}
private:
vector<Parameter*> vect;
};
int main() {
Parameter<int> pi;
Parameter<QString> ps;
pi.setVal(10);
ps.setVal("QString");
P(pi.getVal());
P(ps.getVal().toStdString());
P(pi.getFoo());
P(ps.getFoo());
Container container;
container.push_back(&pi);
container.push_back(&ps);
}
Many thanks to you comments. I will follow your advice and use boost::any.
Here is the updated version :
#include <boost/any.hpp>
#include <QString>
#include <vector>
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
#define P(a) cout << #a << ":" << a << endl
template<typename T> class Parameter {
private:
T val;
public:
void setVal(const T &val) {
this->val = val;
}
const T &getVal() {
return val;
}
string getFoo() {
stringstream s;
s << val;
return s.str();
}
};
template<>
string Parameter<QString>::getFoo() {
stringstream s;
s << val.toStdString();
return s.str();
}
class Container {
public:
void push_back(boost::any base) {
vect.push_back(base);
}
void foo() {
cout << "do something with the parameters\n";
for (vector<boost::any>::iterator i = vect.begin(); i != vect.end(); ++i) {
boost::any a = (*i);
if (a.type() == typeid(Parameter<int>*)) {
Parameter<int> *ai = boost::any_cast<Parameter<int> *>(a);
cout << ai->getFoo() << endl;
} else if (a.type() == typeid(Parameter<QString>*)) {
Parameter<QString> *aq = boost::any_cast<Parameter<QString> *>(a);
cout << aq->getFoo() << endl;
} else {
cout << "unknown type:" << a.type().name() << endl;
}
}
}
private:
vector<boost::any> vect;
};
int main() {
Parameter<int> pi;
Parameter<QString> ps;
pi.setVal(10);
ps.setVal("QString");
P(pi.getVal());
P(ps.getVal().toStdString());
P(pi.getFoo());
P(ps.getFoo());
Container container;
container.push_back(&pi);
container.push_back(&ps);
container.foo();
}
The correct solution is to write good enough interface for the Base class so that you can do everything you need to do:
class Base {
public:
virtual void *GetVal() const=0;
virtual void SetVal(void *ptr)=0;
virtual std::string Type() const=0;
virtual std::string GetAsString() const=0;
};
While this might not be what you want, it still allows passing values from one parameter to the next. Once you want the actual value, you do need to know the type on compile-time. Switch-case for the type might help with making it runtime.
You could use Boost.Any which can hold any type of data. You would then use boost::any_cast<> to convert the object back to the correct type.
Other than that, you'll have to go for the base class approach, but as you mentioned, it could be hard to then make Container::foo do anything useful.
One way you could solve this problem is to have all your foo functions take a string as a parameter, then each specific implementation of the function would parse that string and convert it to the correct type.
Edit: Boost.Any example:
#include <iostream>
#include <boost/any.hpp>
int main()
{
boost::any param = 89;
// This will fail because `param` is currently holding an int
// not a char
char ch = boost::any_cast<char>(param);
// This works
int i = boost::any_cast<int>(param);
// You can always change the value and type of what
// `param` is holding
param = "example";
}
Every thing inside a container has to be the same type. I have done something similar to your approach where I made a base class that had some useful generic interface and the derived class was templated. The only other way to approach a solution would involve defining a base class function to return a value to indicate the type and then downcasting the base.
Is it possible to establish a set of templated function pointers, without the hassle of doing so manually? Here's an example to illustrate what the heck I'm talking about.
Let's say I have a frequently-called function "write" of which I have two implementations (write0 and write1) that I'd like to be able to switch between dynamically. These write functions are templated on the argument type. One way to do this is to just have a templated front-end function write() which internally uses an if statement.
This turns out to be fast enough for my needs, but now I was left wondering if I can do the same using function pointers (just for fun). The problem with this approach is that setting up the function pointers is a hassle. Are there any other ways to essentially achieve the ideal of write() but without the conditional (direct static dispatch)?
(Other "rules": I can't change the Msg classes to have write() methods, and I can't change the use site code to replace Msgs with adaptors for Msgs.)
FWIW, I found this article basically saying the same thing I'm saying here.
#include <iostream>
using namespace std;
template<typename T> void write0(T msg) { cout << "write0: " << msg.name() << endl; }
template<typename T> void write1(T msg) { cout << "write1: " << msg.name() << endl; }
// This isn't so bad, since it's just a conditional (which the processor will
// likely predict correctly most of the time).
bool use_write0;
template<typename T> void write(T msg) { if (use_write0) write0(msg); else write1(msg); }
struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };
// This doesn't work: templates may not be virtual.
#if 0
struct Writer { template<typename T> virtual void write(T msg) = 0; };
struct Writer0 { template<typename T> virtual void write(T msg) { cout << "write0: " << msg.name() << endl; } };
struct Writer1 { template<typename T> virtual void write(T msg) { cout << "write0: " << msg.name() << endl; } };
#endif
int main(int argc, char **argv) {
use_write0 = argc == 1;
// I can do this:
write(MsgA());
// Can I achieve the following without the verbosity (manual setup, named
// template instantiations, etc.)?
void (*pwriteA)(MsgA) = use_write0 ? (void(*)(MsgA)) write0<MsgA> : (void(*)(MsgA)) write1<MsgA>;
void (*pwriteB)(MsgB) = use_write0 ? (void(*)(MsgB)) write0<MsgB> : (void(*)(MsgB)) write1<MsgB>;
void (*pwriteC)(MsgC) = use_write0 ? (void(*)(MsgC)) write0<MsgC> : (void(*)(MsgC)) write1<MsgC>;
void (*pwriteD)(MsgD) = use_write0 ? (void(*)(MsgD)) write0<MsgD> : (void(*)(MsgD)) write1<MsgD>;
pwriteA(MsgA());
pwriteB(MsgB());
pwriteC(MsgC());
pwriteD(MsgD());
return 0;
}
If you want to switch logging functions back and forth while the program runs, I think you have to manually set the function pointer for each type.
If it's enough to just choose the logging function at startup, it can be done in a fully generic way without even knowing for which types the function will be called later:
// writer functions
template<typename T> void write0(T msg) { std::cout << 0; };
template<typename T> void write1(T msg) { std::cout << 1; };
// global flag
bool use_write0;
// function pointers for all types
template<typename T>
struct dispatch {
typedef void (*write_t)(T);
static write_t ptr;
};
// main write function
template<typename T>
inline void write(T msg) {
(*dispatch<T>::ptr)(msg);
}
// the fun part
template<typename T>
void autoinit(T msg) {
if (use_write0)
dispatch<T>::ptr = &write0<T>;
else
dispatch<T>::ptr = &write1<T>;
// call again for dispatch to correct function
write(msg);
}
// initialization
template<typename T>
typename dispatch<T>::write_t dispatch<T>::ptr = &autoinit<T>;
// usage example
int main(int argc, char **argv) {
use_write0 = (argc == 1);
write("abc");
return 0;
}
For each type T the first call to write<T>() decides which writing function should be used. Later calls then directly use the function pointer to that function.
You could also use Don Clugston's FastDelegates header. Generates no runtime overhead whatsoever and truly object-oriented delegates. While the syntax for using them is not perfect, it is a bit simpler than fiddling with raw function pointers.
Why don't you use an array of function pointers?
#include <iostream>
using namespace std;
template<typename T> void write0(T msg) { cout << "write0: " << msg.name() << endl; }
template<typename T> void write1(T msg) { cout << "write1: " << msg.name() << endl; }
template<typename T> struct WriteSelector
{
static void(* const s_functions[])(T msg);
};
template<typename T> void(* const WriteSelector<T>::s_functions[])(T msg)=
{
&write0<T>,
&write1<T>
};
unsigned write_index=0;
template<typename T> void write(T msg)
{
WriteSelector<T>::s_functions[write_index](msg);
}
struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };
void Test()
{
write(MsgA());
write(MsgB());
write(MsgC());
write(MsgD());
}
int main()
{
Test();
write_index=1;
Test();
return 0;
}
There are two axises of variation in writing: the write0/write1 choice and the MsgA/B/C.... choice.
Conceptually that means you need NxM implementations of a write function. Of course, if a write implementation is added, or a message type is added, this leads to resp. M or N extra functions to be added.
For both axises you can choose whether to implement them using static or dynamic polymorphism. The static polymorphism can be done using templates or using function overrides.
It could be done by creating a N element class hierarchy with M write functions in each class. But it would soon become a maintenance nightmare. Unless the message content is also runtime polymorphic. But the question is about static polymorphism for the messages.
Since runtime polymorphism is ruled out because of too elaborate (and you can't have a template function virtual, which would decrease the verbosity of overrides), we need to implement a little type-dispatching routine, converting runtime information into compile-time information.
More specifically: templatize the main action (in the example called Tmain) with the writer-to-use, and call it with the right template argument from the 'real' main.
This omits the use of a 'global' choice variable, yet is object-oriented and concise.
// twodimensionalpolymorph.cpp
//
#include <iostream>
using namespace std;
class Write0 {
public:
template< typename tMsg >
void operator()( /*const*/ tMsg& msg ) { cout << "write0: " << msg.name() << endl; };
};
class Write1 {
public:
template< typename tMsg >
void operator()( /*const*/ tMsg& msg ) { cout << "write1: "<< msg.name() << endl; };
};
struct MsgA { const char *name() { return "MsgA"; } };
struct MsgB { const char *name() { return "MsgB"; } };
struct MsgC { const char *name() { return "MsgC"; } };
struct MsgD { const char *name() { return "MsgD"; } };
// the Tmain does the real action
//
template< typename Writer >
int Tmain( Writer& write, int argc, char** args ) {
write( MsgA() );
write( MsgB() );
write( MsgB() );
write( MsgD() );
return 0;
}
// the main merely chooses the writer to use
//
int main( int argc, char** args ) {
if( argc==1 )
return Tmain( Write0(), argc, args);
else
return Tmain( Write1(), argc, args);
}