How to avoid duplicate code including std::map and std::mutex - c++

I have a class including a map, and a mutex. In every member function the mutex protects the map from multiple thread accessing the object of this class, for ex:
class bar
{
public:
void hello() {}
void set_something(int x) {}
int get_something(int x, int y) { return x + y; }
};
class foo
{
public:
foo()
{
m_map[0];
m_map[1];
m_map[2];
}
void hello(unsigned int index)
{
std::lock_guard<std::mutex> lock(m_mut);
const auto iter = m_map.find(index);
if (iter != m_map.end())
iter->second.hello();
}
void set_something(unsigned int index, int x)
{
std::lock_guard<std::mutex> lock(m_mut);
const auto iter = m_map.find(index);
if (iter != m_map.end())
iter->second.set_something(x);
}
int get_something(unsigned int index, int x, int y)
{
std::lock_guard<std::mutex> lock(m_mut);
const auto iter = m_map.find(index);
if (iter != m_map.end())
return iter->second.get_something(x, y);
return 0;
}
private:
std::mutex m_mut;
std::map<unsigned int, bar> m_map;
};
Is there an elegant method to avoid duplicate code?

You could use a proxy and RAII like this :
#include <iostream>
#include <mutex>
#include <map>
template < typename F, typename S>
struct mutex_map {
std::recursive_mutex m_;
std::map<F,S> map_;
struct Proxy {
Proxy( std::map<F,S> & map, std::recursive_mutex &m ) : map_(&map), lock_(m) {
std::cout << "lock\n";
}
~Proxy() { std::cout << "unlock\n"; }
std::map<F,S>* map_;
std::unique_lock<std::recursive_mutex> lock_;
std::map<F,S>* operator->() { return map_; }
};
Proxy operator->() {
return { map_, m_ };
}
};
int main() {
mutex_map<int, int> mm;
mm->emplace(1, 3);
std::cout << (mm->find(1) == mm->end()) << "\n";
std::cout << (mm->find(2) == mm->end()) << "\n";
}

You can move common part into "do_something" and pass functor into it:
...
void do_something(const std::function<void(bar&)>& func)
{
std::lock_guard<std::mutex> lock(m_mut);
const auto iter = m_map.find(index);
if (iter != m_map.end())
func(std::ref(*iter));
}
int get_something(unsigned int index, int x, int y)
{
do_something(std::bind(&bar::get_something, std::placeholders::_1, index, x, y));
}
...
One question - do you need to protect with mutex only std::map or std::map and its elements? You may think about granularity

Related

how to constract a vector form a data members of other container class

I have a std::array of class A, class A has a data member num_, I want to init a std::vector with all the num_ from the std::array is there a way to do it?. I know that I can loop over it and assign manually, I want to know if there is a way to do it like in my code snippet.
this is what I tried:
#include <iostream>
#include <vector>
#include <array>
class A{
public:
A():num_{0}{}
int num_;
};
int main()
{
std::array<A, 5> arr;
for(int i = 0; i<5; ++i)
{
arr[i].num_ = i;
}
std::vector<int> vec(arr.begin()->num_,arr.end()->num_);
for(auto& i : vec)
{
std::cout << i << " ";
}
return 0;
}
What you want can be done by using a projection iterator: an iterator implemented in terms of another iterator and a projection function - upon access the projection gets applied. For example, using std::ranges::views::transform() (of course, this requires C++20):
#include <ranges>
#include <iostream>
#include <vector>
#include <array>
class A{
public:
A():num_{0}{}
int num_;
};
int main()
{
std::array<A, 5> arr;
for(int i = 0; i<5; ++i)
{
arr[i].num_ = i;
}
auto view = std::ranges::views::transform(arr, [](auto& x){ return x.num_; });
std::vector<int> vec(view.begin(), view.end());
for(auto& i : vec)
{
std::cout << i << " ";
}
return 0;
}
With pre-C++20 the easiest approach to get a std::vector populated with the projected values is probably to not populate the std::vector during construction but rather to populate it via an std::back_inserter (as #john has pointed out in a comment):
std::transform(arr.begin(), arr.end(), std::back_inserter(vec),
[](A& a)->int& { return a.num_; });
To use construction it is necessary to build an iterator doing the relevant projection. It seems std::vector requires homogenous iterator for the begin and end which effectively means that some sort of view needs to be build which provides suitable begin and end iterator. Here is a basic version of this approach:
#include <iostream>
#include <optional>
#include <vector>
#include <array>
#include <type_traits>
class A{
public:
A():num_{0}{}
int num_;
};
template <typename FwdIt, typename Fun>
class projection_iterator
{
mutable FwdIt it;
mutable std::optional<Fun> fun;
public:
using value_type = std::decay_t<decltype(std::declval<Fun>()(*std::declval<FwdIt>()))>;
using reference = value_type&;
using pointer = value_type*;
using difference_type = typename std::iterator_traits<FwdIt>::difference_type;
using iterator_category = std::forward_iterator_tag;
projection_iterator(): it(), fun(fun) {}
projection_iterator(FwdIt it, Fun fun): it(it), fun(fun) {}
reference operator*() const { return (*this->fun)(*this->it); }
pointer operator->() const { return &(*this->fun)(*this->it); }
projection_iterator& operator++() { ++this->it; return *this; }
projection_iterator operator++(int) { auto rc(*this); ++this->it; return rc; }
bool operator== (projection_iterator const& other) const { return this->it == other.it; }
bool operator!= (projection_iterator const& other) const { return this->it != other.it; }
bool operator== (FwdIt const& other) const { return this->it == other; }
bool operator!= (FwdIt const& other) const { return this->it != other; }
};
template <typename Range, typename Fun>
class project_range {
Range& range;
Fun fun;
template <typename FwdIt>
static auto make(FwdIt it, Fun fun) {
return projection_iterator<FwdIt, Fun>(it, fun);
}
public:
project_range(Range& range, Fun fun): range(range), fun(fun) {}
project_range(std::decay_t<Range>&&, Fun) = delete;
auto begin() { return make(range.begin(), fun); }
auto end() { return make(range.end(), fun); }
};
template <typename Range, typename Fun>
auto project(Range&& range, Fun fun) {
return project_range<Range, Fun>(range, fun);
}
int main()
{
std::array<A, 5> arr;
for(int i = 0; i<5; ++i)
{
arr[i].num_ = i;
}
auto p = project(arr, [](A& x)->int& { return x.num_; });
std::vector<int> vec(p.begin(), p.end());
for(auto& i : vec)
{
std::cout << i << " ";
}
return 0;
}
Unless absolutely necessary I wouldn't bother going down that route with pre-C++20 compilers.

C++ Similar functions using different data types

I have two functions which are exactly the same, except that one of them uses a stack for its operations and the other one uses a queue:
void doQueue()
{
std::queue<int> q;
...
...
q.push(someValue);
...
...
int tmp = q.front();
q.pop()
}
void doStack()
{
std::stack<int> s;
...
...
s.push(someValue);
...
...
int tmp = s.top();
s.pop()
}
I want to eliminate duplicate code. As queue uses the front function to retrieve the first value and stack uses the top function, I thought that templates may not work since functions with different names have to be called.
My other idea was to create an interface which will be as a wrapper to both data structures and I can pass around the one that I need.:
class Data
{
public:
virtual void push(const int v) = 0;
virtual int pop() = 0;
};
class StackData : public Data
{
private:
std::stack<int> _stack;
public:
virtual void push(const int v) {_stack.push(v);}
virtual int pop()
{
int ret = _stack.top();
_stack.pop();
return ret;
}
};
class QueueData : public Data
{
private:
std::queue<int> _queue;
public:
virtual void push(const int v) {_queue.push(v);}
virtual int pop()
{
int ret = _queue.front();
_queue.pop();
return ret;
}
};
void doData(Data& dataType)
{
...
dataType.push(someValue);
...
int tmp = dataType.pop();
}
void doQueue()
{
QueueData queueData;
doData(queueData);
}
void doStack()
{
StackData stackData;
doData(stackData);
}
But I think there should be an easier and better way to perform this operation.
Here's one way - a wrapper template with partial specialisation on underlying container type:
#include <stack>
#include <queue>
template<class Container>
struct generic_sequence_ops;
template<class T, class UnderlyingContainer>
struct generic_sequence_ops<std::stack<T, UnderlyingContainer>>
{
using container_type = std::stack<T, UnderlyingContainer>;
using value_type = typename container_type::value_type;
generic_sequence_ops(container_type& c) : c(c) {}
void add_one(value_type v)
{
c.push(std::move(v));
}
void remove_one()
{
c.pop();
}
value_type& current()
{
return c.top();
}
container_type& c;
};
template<class T, class UnderlyingContainer>
struct generic_sequence_ops<std::queue<T, UnderlyingContainer>>
{
using container_type = std::queue<T, UnderlyingContainer>;
using value_type = typename container_type::value_type;
generic_sequence_ops(container_type& c) : c(c) {}
void add_one(value_type v)
{
c.push(std::move(v));
}
void remove_one()
{
c.pop();
}
value_type& current()
{
return c.back();
}
container_type& c;
};
template<class Container>
auto make_generic_sequence_ops(Container& cont)
{
return generic_sequence_ops<std::decay_t<Container>>(cont);
}
template<class Container>
int doContainer(Container& cont)
{
auto s = make_generic_sequence_ops(cont);
s.add_one(6);
int val = s.current();
s.remove_one();
return val;
}
int main()
{
std::queue<int> q;
std::stack<int> s;
doContainer(q);
doContainer(s);
}

Using class member variable as reference

I would like to have a class member variable to be able to switch in between items in a map so that when it is modified, the content of the map is also modified.
Is there any way other than to use a pointer to the content of the map ? Old code only needed only variable, now the new one needs to switch. If I change the variable type, then all functions using this member variable need to be changed. Not complicated, but I would find it ugly to have * in front of it everywhere...
A reference variable cannot be rebound, so how can I achieve this ?
class A
{
std::map<std::string,std::vector<int>> mMyMap;
std::vector<int>& mCurrentVector;
std::vector<int>* mCurrentVectorPointer;
std::vector<int> mDefaultVector;
void setCurrentVector(int iKey);
void addToCurrentVector(int iValue);
}
A::A():
mDefaultVector(std::vector<int>())
mCurrentVector(mDefaultVector)
{
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
mCurrentVectorPointer = &mMyMap[0];
}
A::setCurrentVector(std::string iKey)
{
if(mMyMap.find(iKey) != mMyMap.end())
{
mCurrentVector = mMyMap[iKey]; //can't change a reference...
mCurrentVectorPointer = &mMyMap[iKey]; //could use pointer, but
}
}
A::addToCurrentVector(int iValue)
{
mCurrentVector.push_back(iValue);
//or
(*mCurrentVectorPointer).push_back(iValue);
//
mCurrentVectorPointer->push_back(iValue);
}
void main()
{
A wClassA();
wClassA.setCurrentVector("key2");
wClassA.addToCurrentVector(3);
wClassA.setCurrentVector("key1");
wClassA.addToCurrentVector(4);
}
mMyMap["key1"] now contains 1,4
mMyMap["key2"] now contains 2,3
You can't reseat a reference once it has been assigned which means you are left with using your other option, a pointer.
As I understand it, you're refactoring some existing code which only used a single vector, whereas now you need a map of vectors.
You're trying to achieve this with minimal modifications, and keeping the interface to the vector the same.
An option would be to use a local reference, assigned from your pointer.
class A
{
using Vector = std::vector<int>;
public:
A()
{
map_["key1"] = std::vector<int>(1,1);
map_["key2"] = std::vector<int>(1,2);
curr_vec_ = &map_["key1"];
}
void setCurrentVector(const std::string& key)
{
if(map_.find(key) != map_.end())
{
curr_vec_ = &map_[key];
}
}
void addToCurrentVector(int val)
{
assert(curr_vec_);
Vector& curr_vec = *curr_vec_; // local reference
curr_vec.push_back(val);
curr_vec[0] = 2;
// etc
}
private:
std::map<std::string, Vector> map_;
Vector* curr_vec_ = nullptr;
}
You may write some wrapper:
#define Return(X) noexcept(noexcept(X)) -> decltype(X) { return X; }
template <typename U>
class MyVectorRef
{
private:
std::vector<U>* vec = nullptr;
public:
explicit MyVectorRef(std::vector<U>& v) : vec(&v) {}
void reset(std::vector<U>& v) {vec = &v;}
// vector interface
auto at(std::size_t i) const Return(vec->at(i))
auto at(std::size_t i) Return(vec->at(i))
auto operator [](std::size_t i) const Return(vec->operator[](i))
auto operator [](std::size_t i) Return(vec->operator[](i))
template <typename ... Ts> auto assign(Ts&&... ts) Return(vec->assign(std::forward<Ts>(ts)...))
auto assign( std::initializer_list<U> ilist ) Return(vec->assign(ilist))
template <typename T> auto push_back(T&& t) const Return(vec->push_back(std::forward<T>(t)))
template <typename T> auto emplace_back(T&& t) const Return(vec->emplace_back(std::forward<T>(t)))
auto begin() const Return(vec->begin())
auto begin() Return(vec->begin())
auto end() const Return(vec->end())
auto end() Return(vec->end())
auto cbegin() const Return(vec->cbegin())
auto cend() const Return(vec->cend())
// ...
};
and then, use it:
class A
{
public:
A() : mCurrentVector(mDefaultVector) {
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
}
std::map<std::string, std::vector<int>> mMyMap;
std::vector<int> mDefaultVector;
MyVectorRef<int> mCurrentVector;
void setCurrentVector(std::string iKey)
{
auto it = mMyMap.find(iKey);
if (it != mMyMap.end())
{
mCurrentVector.reset(it->second);
}
}
void addToCurrentVector(int iValue)
{
mCurrentVector.push_back(iValue);
}
};
But I think it would be simpler to just create a getter in A and use directly a pointer:
class A
{
public:
A() : mCurrentVector(&mDefaultVector) {
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
}
std::map<std::string, std::vector<int>> mMyMap;
std::vector<int> mDefaultVector;
std::vector<int>* mCurrentVector;
std::vector<int>& GeCurrentVector() { return *mCurrentVector; }
void setCurrentVector(std::string iKey)
{
auto it = mMyMap.find(iKey);
if (it != mMyMap.end())
{
mCurrentVector = &it->second;
}
}
void addToCurrentVector(int iValue)
{
GeCurrentVector().push_back(iValue);
}
};

C++ - Map of Vectors of Smart Pointers - All inherited from the same base class

I've got this Map in my Entity-Component-System:
std::map<u_int32_t, std::vector<std::shared_ptr<Component>>> _componentMap;
The u_int32_t is the key to a vector of components. There can be multiple instances of the same component. (That's why there's a vector).
Now I would like to have a templated getter-function that returns a Vector of an inherited type:
template<class T> inline const std::vector<std::shared_ptr<T>> & getVector() const
{
u_int32_t key = getKey<T>();
return static_cast<std::vector<std::shared_ptr<T>>>(_componentMap.count(key) ? _componentMap.at(key) : _emptyComponentVec);
}
I know that this doesn't work, since std::vectors of different types are completely unrelated and I cannot cast between them. I would also like to avoid allocating a new vector every time this function is called.
But how I can I get the desired behaviour? When the the components are added I can create an std::vector of the desired derived type.
The question could also be: How can I have an std::map containing different types of std::vector?
For any solutions I can not link against boost, though if absolutely needed, I could integrate single headers of boost.
template<class It>
struct range_view {
It b, e;
It begin() const { return b; }
It end() const { return e; }
using reference = decltype(*std::declval<It const&>());
reference operator[](std::size_t n) const
{
return b[n];
}
bool empty() const { return begin()==end(); }
std::size_t size() const { return end()-begin(); }
reference front() const {
return *begin();
}
reference back() const {
return *std::prev(end());
}
template<class O>
range_view( O&& o ):
b(std::begin(o)), e(std::end(o))
{}
};
this is a quick range view. It can be improved.
Now all you need to do is write a pseudo-random-access iterator that converts its arguments. So it takes a random access iterator over a type T, then does some operation F to return a type U. It forwards all other operations.
The map then stores std::vector<std::shared_ptr<Base>>. The gettor returns a range_view< converting_iterator<spBase2spDerived> >.
Here is a crude implementation of a solution I have in mind for this problem. Of course, there are many rooms to refine the code, but hopefully it conveys my idea.
#include <iostream>
#include <map>
#include <vector>
#include <memory>
using namespace std;
class Base {
public:
virtual void f() const = 0;
};
class A : public Base {
public:
static const int type = 0;
explicit A(int a) : a_(a) {}
void f() const { cout << "calling A::f" << endl;}
int a_;
};
class B : public Base {
public:
static const int type = 1;
explicit B(int a) : a_(a) {}
void f() const { cout << "calling B::f" << endl;}
int a_;
};
class MapWrapper {
public:
template<class T>
void append(int a, vector<T> const& vec) {
types_[a] = T::type;
my_map_[a] = make_shared<vector<T>>(vec);
}
template<class T>
vector<T> const& get(int a) const {
return *static_pointer_cast<vector<T>>( my_map_.at(a) );
}
map<int, shared_ptr<void>> const& get_my_map() const {
return my_map_;
}
vector<shared_ptr<Base>> get_base(int a) const {
vector<shared_ptr<Base>> ret;
switch(types_.at(a)) {
case 0: {
auto const vec = get<A>(a);
for(auto v : vec)
ret.push_back(make_shared<A>(v));
break;
}
case 1: {
auto const vec = get<B>(a);
for(auto v : vec)
ret.push_back(make_shared<B>(v));
break;
}
}
return ret;
}
map<int, shared_ptr<void>> my_map_;
map<int, int> types_;
};
int main() {
MapWrapper map_wrapper;
map_wrapper.append(10, vector<A>{A(2), A(4)});
map_wrapper.append(20, vector<B>{B(5), B(7), B(9)});
for(auto const& w : map_wrapper.get_my_map())
for(auto v : map_wrapper.get_base(w.first))
v->f();
for(auto const& x: map_wrapper.get<A>(10))
cout << x.a_ << " ";
cout << endl;
for(auto const& x: map_wrapper.get<B>(20))
cout << x.a_ << " ";
return 0;
}
The solution was to use reinterpret_cast:
template<class T> inline std::vector<std::shared_ptr<T>> * getVector() const
{
auto key = getKey<T>();
return reinterpret_cast<std::vector<std::shared_ptr<T>> *>( (_componentMap.count(key) ? _componentMap.at(key).get() : const_cast<std::vector<std::shared_ptr<Component>> *>(&_emptyComponentSharedPtrVec)) );
}
It's not very pretty but it does work fine and it fulfills all requirements.

"pure virtual function call" error on Debug ONLY

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