I am writing an event system for a game engine, and I need a way to 'disconnect' functions from the event (erase from std::vector) but I also require that to remove an event the developer must give a valid reference to the function they wish to disconnect.
At the moment I have a class like this:
template<typename ... Args>
class event{
public:
using delegate_type = std::function<void(Args...)>;
void operator()(Args ... args){
for(auto &&f : m_funcs)
f(args...);
}
template<typename FunctorType>
delegate_type &connect(FunctorType &&f){
m_funcs.emplace_back(f);
return m_funcs.back();
}
bool disconnect(delegate_type &f){
for(auto iter = begin(m_funcs); iter != end(m_funcs); ++iter)
if(&(*iter) == &f){ // if the dev passed a valid function
m_funcs.erase(iter);
return true;
}
return false;
}
private:
std::vector<delegate_type> m_funcs;
};
But this of-course suffers from reference invalidation whenever the underlying vector is resized after a disconnect or connect operation.
I tried switching to an std::list solution rather than std::vector but the speed difference when iterating over the functions is detrimental enough that I can not make that switch in a release build.
Is there some way I can avoid the invalidation using a helper class or identifier instead of a straight reference?
I suggest you to take a look at Boost.Signals2. When connecting, it returns a sigc::connection object. You can keep it client-side and use it for disconnecting or it will disconnect automatically the slot when it is destroyed. It does not suffer from the reference invalidation problem.
Store weak_ptr<delegate_type>. Remove invalid ones on each invocation (remove_if). (operator()). Guard agains recursive invocation somehow as an aside.
Return shared_ptr<delegate_type> as your token. The external user simply .reset()s to unregister. No need to disconnect!
Allocate with make_shared<delegate_type> within connect.
Make connect a template that perfect forwards into the make_shared.
Use an associative container, like the std::list you were using.
But, you'll need a more complicated use of the container so you can eliminate the overhead of looping over it.
The best solution is of course just removing the need of the loop.
The most efficient way, would be to then return an iterator for the container's cell instead of the function itself.
A remove like this would avoid the search, because you'll have the iterator location already.
In associative containers the iterators don't get invalidated (But beware, it's not thread safe of course).
template<typename FunctorType>
std::list<delegate_type>::iterator &connect(FunctorType &&f){
m_funcs.emplace_back(f);
//get the iterator for the last item
return m_funcs.end()--;
}
bool disconnect(std::list<delegate_type>::iterator &f){
//direct removal
m_funcs.erase(f);
return true;
}
Related
CppCheck suggest me to replace one of my code by a STL algorithm, I'm not against it, but I don't know how to replace it. I'm pretty sure this is a bad suggestion (There is warning about experimental functionalities in CppCheck).
Here is the code :
/* Cutted beginning of the function ... */
for ( const auto & program : m_programs )
{
if ( program->compare(vertexShader, tesselationControlShader, tesselationEvaluationShader, geometryShader, fragmentShader) )
{
TraceInfo(Classname, "A program has been found matching every shaders.");
return program;
}
}
return nullptr;
} /* End of the function */
And near the if condition I got : "Consider using std::find_if algorithm instead of a raw loop."
I tried to use it, but I can't get the return working anymore... Should I ignore this suggestion ?
I suppose you may need to use that finding function not once. So, according to DRY, you need to separate the block where you invoke an std::find_if algorithm to a distinct wrapper function.
{
// ... function beginning
auto found = std::find_if(m_programs.cbegin(), m_programs.cend(),
[&](const auto& prog)
{
bool b = prog->compare(...);
if (b)
TraceInfo(...);
return b;
});
if (found == m_programs.cend())
return nullptr;
return *found;
}
The suggestion is good. An STL algorithm migth be able to choose an appropriate
approach based on your container type.
Furthermore, I suggest you to use a self-balancing container like an std::set.
// I don't know what kind of a pointer you use.
using pProgType = std::shared_pointer<ProgType>;
bool compare_progs(const pProgType &a, const pProgType &b)
{
return std::less(*a, *b);
}
std::set<std::shared_pointer<prog_type>,
std::integral_constant<decltype(&compare_progs), &compare_progs>> progs.
This is a sorted container, so you will spend less time for searching a program by a value, given you implement a compare operator (which is invoked by std::less).
If you can use an stl function, use it. This way you will not have to remember what you invented, because stl is properly documented and safe to use.
I have a Server class that processes QJsonObject data and handles it according to a key set in the data.
At the moment, I use a big if-then-else statement to decide what to do like this:
const QString action = jsonObject.value(KEY_ACTION).toString();
if (action == SOME_ACTION) {
// do something
} else if (action == SOME_OTHER_ACTION) {
// do something else
}
and so on. Now, meanwhile, I have quite a lot of actions, and for each one, my server has to check all cases until it finds the correct one. I thus wondered if there was a nicer way to do this.
I thought about having the data processing in different functions and having a QHash with the respective function pointer to the respective function for each action like this:
In the constructor:
const QHash<QString, void(Server::*)(const QJsonObject &)> processFunctionsMap {
{ SOME_ACTION, &Server::processSomeAction },
{ SOME_OTHER_ACTION, &Server::processSomeOtherAction }
}
And the respective functions:
void Server::processSomeAction(const QJsonObject &data)
{
...
}
and then to invoke the matching function:
if (! processFunctionsMap.contains(action)) {
// Catch this case
}
(this->*processFunctionsMap.value(action))(jsonObject);
This seems to work, but I'm not a C++ pro, so my question is if this is the correct way to do it.
Your approach is reasonable, but you've changed the no-matches scenario from executing an else block (potentially doing nothing at all) to instant undefined behavior.
You need to separate the hash lookup from the call so you can insert a check for successful lookup in between. With C++ standard collections (std::map which is a red-black tree, std::unordered_map which is a hashtable), that'd be a call to find(key) which returns an iterator... you compare it to map.end() and make very sure not to dereference if they are equal. QHash, or any other non-standard hashtable, will surely provide something similar.
Understanding what QHash does when key not found
I have the following code:
class CEvent
{
public:
CEvent(std::string const&) {}
};
std::unordered_map<std::string, CEvent> m_messageList;
CEvent& GetMessageEvent(std::string const& name)
{
auto it = m_messageList.find(name);
if (it == m_messageList.end())
{
auto pair = m_messageList.emplace(std::piecewise_construct,
std::forward_as_tuple(name), // Copy-construct 'name' as the key
std::forward_as_tuple(name)); // Construct CEvent in-place with the name
return pair.first->second;
}
return it->second;
}
(Live Sample)
I think the code is pretty clean, but I don't like that I have to do a find separate from emplace. Is there a way to do this better? Or is this "good enough"? I know I could probably call emplace instead of find first, to accomplish both tasks, but this means creating a CEvent every time, even if no real insert happens.
Once C++17 is released (or if your compiler supports prerelease versions),
return m_messageList.try_emplace(name, name).first; should do the trick.
Let Action be a class with a is_finished method and a numeric tag property.
Let this->vactions be a std::vector<Action>
The intent is to iterate the vector and identify those Actions who are finished,
store their tags in a std::vector<unsigned int> and delete the actions.
I tried to play with lambdas and a little and came up with a little
code that read nicely but caused memory corruptions. The "extended" version,
on the other hand, works as expected.
I suspect foul play in the remove_if part, but for the life of me I can't figure
out what's wrong.
Here's the example code.
This causes memory corruptions
std::vector<unsigned int> tags;
auto is_finished=[p_delta](Action& action) -> bool {return action.is_finished();};
//This is supposed to put the finished actions at the end of the vector and return
//a iterator to the first element that is finished.
std::vector<Action>::iterator nend=remove_if(this->vactions.begin(), this->vactions.end(), is_finished);
auto store_tag=[&tags](Action& action)
{
if(action->has_tag())
{
tags.push_back(action->get_tag());
}
};
//Store the tags...
for_each(nend, this->vactions.end(), store_tag);
//Erase the finished ones, they're supposed to be at the end.
this->vaction.erase(nend, this->vaction.end());
if(tags.size())
{
auto do_something=[this](unsigned int tag){this->do_something_with_tag(tag);};
for_each(tags.begin(), tags.end(), do_something);
}
This, on the other side, works as expected
std::vector<Action>::iterator ini=this->vactions.begin(),
end=this->vactions.end();
std::vector<unsigned int> tags;
while(ini < end)
{
if( (*ini).is_finished())
{
if((*ini).has_tag())
{
tags.push_back((*ini).get_tag());
}
ini=this->vaction.erase(ini);
end=this->vaction.end();
}
else
{
++ini;
}
}
if(tags.size())
{
auto do_something=[this](unsigned int tag){this->do_something_with_tag(tag);};
for_each(tags.begin(), tags.end(), do_something);
}
I am sure there's some rookie mistake here. Can you help me spot it?.
I thought that the for_each could be updating my nend iterator but found
no information about it. What if it did? Could the vector try to erase beyond the "end" point?.
std::remove_if does not preserve the values of the elements that are to be removed (See cppreference). Either get the tag values before calling remove_if - as you do in the second case - or use std::partition instead.
I've stumbled across this great post about validating parameters in C#, and now I wonder how to implement something similar in C++. The main thing I like about this stuff is that is does not cost anything until the first validation fails, as the Begin() function returns null, and the other functions check for this.
Obviously, I can achieve something similar in C++ using Validate* v = 0; IsNotNull(v, ...).IsInRange(v, ...) and have each of them pass on the v pointer, plus return a proxy object for which I duplicate all functions.
Now I wonder whether there is a similar way to achieve this without temporary objects, until the first validation fails. Though I'd guess that allocating something like a std::vector on the stack should be for free (is this actually true? I'd suspect an empty vector does no allocations on the heap, right?)
Other than the fact that C++ does not have extension methods (which prevents being able to add in new validations as easily) it should be too hard.
class Validation
{
vector<string> *errors;
void AddError(const string &error)
{
if (errors == NULL) errors = new vector<string>();
errors->push_back(error);
}
public:
Validation() : errors(NULL) {}
~Validation() { delete errors; }
const Validation &operator=(const Validation &rhs)
{
if (errors == NULL && rhs.errors == NULL) return *this;
if (rhs.errors == NULL)
{
delete errors;
errors = NULL;
return *this;
}
vector<string> *temp = new vector<string>(*rhs.errors);
std::swap(temp, errors);
}
void Check()
{
if (errors)
throw exception();
}
template <typename T>
Validation &IsNotNull(T *value)
{
if (value == NULL) AddError("Cannot be null!");
return *this;
}
template <typename T, typename S>
Validation &IsLessThan(T valueToCheck, S maxValue)
{
if (valueToCheck < maxValue) AddError("Value is too big!");
return *this;
}
// etc..
};
class Validate
{
public:
static Validation Begin() { return Validation(); }
};
Use..
Validate::Begin().IsNotNull(somePointer).IsLessThan(4, 30).Check();
Can't say much to the rest of the question, but I did want to point out this:
Though I'd guess that allocating
something like a std::vector on the
stack should be for free (is this
actually true? I'd suspect an empty
vector does no allocations on the
heap, right?)
No. You still have to allocate any other variables in the vector (such as storage for length) and I believe that it's up to the implementation if they pre-allocate any room for vector elements upon construction. Either way, you are allocating SOMETHING, and while it may not be much allocation is never "free", regardless of taking place on the stack or heap.
That being said, I would imagine that the time taken to do such things will be so minimal that it will only really matter if you are doing it many many times over in quick succession.
I recommend to get a look into Boost.Exception, which provides basically the same functionality (adding arbitrary detailed exception-information to a single exception-object).
Of course you'll need to write some utility methods so you can get the interface you want. But beware: Dereferencing a null-pointer in C++ results in undefined behavior, and null-references must not even exist. So you cannot return a null-pointer in a way as your linked example uses null-references in C# extension methods.
For the zero-cost thing: A simple stack-allocation is quite cheap, and a boost::exception object does not do any heap-allocation itself, but only if you attach any error_info<> objects to it. So it is not exactly zero cost, but nearly as cheap as it can get (one vtable-ptr for the exception-object, plus sizeof(intrusive_ptr<>)).
Therefore this should be the last part where one tries to optimize further...
Re the linked article: Apparently, the overhaead of creating objects in C# is so great that function calls are free in comparison.
I'd personally propose a syntax like
Validate().ISNOTNULL(src).ISNOTNULL(dst);
Validate() contructs a temporary object which is basically just a std::list of problems. Empty lists are quite cheap (no nodes, size=0). ~Validate will throw if the list is not empty. If profiling shows even this is too expensive, then you just change the std::list to a hand-rolled list. Remember, a pointer is an object too. You're not saving an object just by sticking to the unfortunate syntax of a raw pointer. Conversely, the overhead of wrapping a raw pointer with a nice syntax is purely a compile-time price.
PS. ISNOTNULL(x) would be a #define for IsNotNull(x,#x) - similar to how assert() prints out the failed condition, without having to repeat it.