Skip to main content
Code Review

Return to Answer

replaced http://stackoverflow.com/ with https://stackoverflow.com/
Source Link
  • Holding a reference (even if its a const ref) would limit how freely you can use this struct. Namely, it becomes unassignable, thus makes it unusable with std::remove_if, among other limitations. The alternative, which is to store a pointer, becomes unsafe as soon as that std::function goes out of scope and destructs.
  • Since Event::Type names a type, you need to prefix it with typename. A solution would be to create an alias declaration using EventType = typename Event::Type; within your class. For more info on typename see here here
  • Holding a reference (even if its a const ref) would limit how freely you can use this struct. Namely, it becomes unassignable, thus makes it unusable with std::remove_if, among other limitations. The alternative, which is to store a pointer, becomes unsafe as soon as that std::function goes out of scope and destructs.
  • Since Event::Type names a type, you need to prefix it with typename. A solution would be to create an alias declaration using EventType = typename Event::Type; within your class. For more info on typename see here
  • Holding a reference (even if its a const ref) would limit how freely you can use this struct. Namely, it becomes unassignable, thus makes it unusable with std::remove_if, among other limitations. The alternative, which is to store a pointer, becomes unsafe as soon as that std::function goes out of scope and destructs.
  • Since Event::Type names a type, you need to prefix it with typename. A solution would be to create an alias declaration using EventType = typename Event::Type; within your class. For more info on typename see here
Added pesky last parameter to erase-remove idiom
Source Link
auto& subscribers = observers_[event];
auto to_remove = std::remove_if(subscribers.begin(),subscribers.end(),[sub=subscription](const auto& observer)
 {
 return observer.id == sub;
 });
if(to_remove == subscribers.end())
{
 // consider throwing an exception instead of assert(false)
}
subscribers.erase(to_remove,subscribers.end());
auto& subscribers = observers_[event];
auto to_remove = std::remove_if(subscribers.begin(),subscribers.end(),[sub=subscription](const auto& observer)
 {
 return observer.id == sub;
 });
if(to_remove == subscribers.end())
{
 // consider throwing an exception instead of assert(false)
}
subscribers.erase(to_remove);
auto& subscribers = observers_[event];
auto to_remove = std::remove_if(subscribers.begin(),subscribers.end(),[sub=subscription](const auto& observer)
 {
 return observer.id == sub;
 });
if(to_remove == subscribers.end())
{
 // consider throwing an exception instead of assert(false)
}
subscribers.erase(to_remove,subscribers.end());
Source Link

There are a few things I'd consider changing in your code, especially since you state that you'd like to transition to modern C++.

Using struct variables in C++ doesn't require prefixing them with the struct keyword as is needed in C, so your typedef struct {...} Callable2;, which I imagine you wrote so that you could get away with Callable2 foo; as opposed to struct Callable2 foo; could be changed to simply struct Callable2 {...}; without any change in the usage of that struct.

In your subscribe member function, you take as one of your parameters, an rvalue reference to std::function<void(const Event&)> - in other words, a temporary. However, you don't subsequently move the temporary so as to take advantage of possible optimizations that moveing a std::function could have as opposed to copying it. You could remedy this by initializing this_call with {std::move(call),++last_id_}. However, even with this, you'll still end up pushing back a copy of the Callable2 you just created and effectively nullify the benefit of moveing the std::function. An alternative approach you could perhaps consider would be:

Callable2 this_call = { std::move(call), ++last_id_ };
observers_[event].push_back(std::move(this_call));
return last_id_;

Which avoids the copy of the std::function into the vector. But this still requires a move of a Callable2. Perhaps an even better approach would be:

observers_[event].emplace_back(std::move(call),++last_id_);
return last_id_;

And the only move done is on the std::function. This would require you, however, to add the appropriate constructor within Callable2, but it does the least amount of operations regarding copying and moving.

Your unsubscribe function could also benefit from the erase-remove idiom, which is by no means "modern" advice but still does the job possibly more efficiently and becomes much more readable and easier to maintain. By looking at unsubscribe it appears you want to purge an event with a specific ID and signal some emergency (which you're doing by asserting false) if the event doesn't exist. With the erase-remove idiom, unsubscribe would instead look like this:

auto& subscribers = observers_[event];
auto to_remove = std::remove_if(subscribers.begin(),subscribers.end(),[sub=subscription](const auto& observer)
 {
 return observer.id == sub;
 });
if(to_remove == subscribers.end())
{
 // consider throwing an exception instead of assert(false)
}
subscribers.erase(to_remove);

This more clearly expresses your intent than manually looping, comparing, etc. You can even opt for std::find_if which stops as soon as it finds the first occurrence of a condition you specify, which might be more efficient than std::remove_if depending upon the size of your events vector. I also mentioned throwing an exception rather than doing a blatant assert(false) for a whole host of reasons. I point you to Jon Kalb's excellent 3-part talk at CppCon for matters regarding exceptions, exception safe code, and its benefits.

You didn't quite express if your events need to be ordered. In the case that they don't, consider using a std::unordered_map to map your Events, which provides a hash-table container as opposed to a balanced binary search tree that std::map gives you. This would also require that EventType be hashable.

On a minor note, at the beginning of your code, you're typedef-ing a SUBSCRIPTION_ID for an int, but there's honestly little need for typedefs in modern C++ unless you're working with an older codebase. An alias declaration not only gives you the same semantics and behavior as a typedef, but also comes in handy if you ever want to have alias templates, which requires more boilerplate code if it were done with the equivalent typedef, not to mention being more readable. Use using SUBSCRIPTION_ID = int; to replace your current typedef.

To answer your questions,

  • Holding a reference (even if its a const ref) would limit how freely you can use this struct. Namely, it becomes unassignable, thus makes it unusable with std::remove_if, among other limitations. The alternative, which is to store a pointer, becomes unsafe as soon as that std::function goes out of scope and destructs.
  • Since Event::Type names a type, you need to prefix it with typename. A solution would be to create an alias declaration using EventType = typename Event::Type; within your class. For more info on typename see here
lang-cpp

AltStyle によって変換されたページ (->オリジナル) /