Value loses his Listener


#1

Hi, I’m trying to write a multithread program with the juce library and I’m having some trouble with the Value::Listener class. I’ve created a class called threadSafeValue, which contains a Value and a mutex to regulate the access to the value, and than I store some of this objects in a std::vector m_linesState. I associate a listenrer to each one, but, during the process, one of the Values loses his listener.
how you can see in the gdb report of the storing process, at line 117, when I push back the second object, the one I stored before (referencedObject = 0xef8120) in m_linesState loses his Listener and numUsed passes from 1 to 0.

116	    for(int i=0; i<activedLinePin.size();++i){
(gdb) n
117	        m_linesState.push_back(threadSafeValue(var(false)));
(gdb) n
118	        m_linesState[i].addListener(this);
(gdb) n
119	        m_signals.push_back(new Circle(0xffff0000));
(gdb) 
120	        addChildComponent(m_signals[i]);
(gdb) 
116	    for(int i=0; i<activedLinePin.size();++i){
(gdb) print m_linesState 
$11 = std::vector of length 1, capacity 1 = {{value = {value = {
    referencedObject = 0xef8120}, listeners = {listeners = {
      data = {<juce::DummyCriticalSection> = {<No data fields>}, elements = {
          data = 0xf07320}, numAllocated = 8}, numUsed = 1}}}, 
mutex = std::shared_ptr (count 1, weak 0) 0xed7490}}
(gdb) n
117	        m_linesState.push_back(threadSafeValue(var(false)));
(gdb) print m_linesState 
$12 = std::vector of length 1, capacity 1 = {{value = {value = {
    referencedObject = 0xef8120}, listeners = {listeners = {
      data = {<juce::DummyCriticalSection> = {<No data fields>}, elements = {
          data = 0xf07320}, numAllocated = 8}, numUsed = 1}}}, 
mutex = std::shared_ptr (count 1, weak 0) 0xed7490}}
(gdb) n
118	        m_linesState[i].addListener(this);
(gdb) print m_linesState 
$13 = std::vector of length 2, capacity 2 = {{value = {value = {
    referencedObject = 0xef8120}, listeners = {listeners = {
      data = {<juce::DummyCriticalSection> = {<No data fields>}, elements = {
          data = 0x0}, numAllocated = 0}, numUsed = 0}}}, 
mutex = std::shared_ptr (count 1, weak 0) 0xed7490}, {value = {value = {
    referencedObject = 0xf3f5a0}, listeners = {listeners = {
      data = {<juce::DummyCriticalSection> = {<No data fields>}, elements = {
          data = 0x0}, numAllocated = 0}, numUsed = 0}}}, 
mutex = std::shared_ptr (count 1, weak 0) 0xf06f50}}
(gdb) n
119	        m_signals.push_back(new Circle(0xffff0000));
(gdb) print m_linesState 
$14 = std::vector of length 2, capacity 2 = {{value = {value = {
    referencedObject = 0xef8120}, listeners = {listeners = {
      data = {<juce::DummyCriticalSection> = {<No data fields>}, elements = {
          data = 0x0}, numAllocated = 0}, numUsed = 0}}}, 
mutex = std::shared_ptr (count 1, weak 0) 0xed7490}, {value = {value = {
    referencedObject = 0xf3f5a0}, listeners = {listeners = {
      data = {<juce::DummyCriticalSection> = {<No data fields>}, elements = {
          data = 0xf07440}, numAllocated = 8}, numUsed = 1}}}, 
mutex = std::shared_ptr (count 1, weak 0) 0xf06f50}}

the strange fact is that this problem appears only the first time this section of the code is executed. if I clear the vector and recall the same function it works fine. how can I resolve it?

here are the codes of threadSafeValue.h

#include "../JuceLibraryCode/JuceHeader.h" 
#include <memory> 
 
using namespace std; 
 
class threadSafeValue { 
    private: 
        Value value; 
        shared_ptr<CriticalSection> mutex; 
     
    public: 
        threadSafeValue(const var&); 
        threadSafeValue(); 
        ~threadSafeValue(); 
        var getValue(); 
        void setValue(const var&); 
        void referTo (threadSafeValue&); 
        void addListener (Value::Listener*); 
        shared_ptr<CriticalSection>& getMutex(); 
        Value& getUnsafeValue(); 
        void removeListener (Value::Listener*); 
}; 

threadSafeValue.cpp

threadSafeValue::threadSafeValue():value(),mutex(new CriticalSection()){ 
} 
 
threadSafeValue::threadSafeValue(const var& newValue):value(newValue), mutex(new CriticalSection()){ 
} 
 
threadSafeValue::~threadSafeValue(){ 
} 
 
void 
threadSafeValue::setValue(const var& newValue){ 
    mutex->enter(); 
    value.setValue(newValue); 
    mutex->exit(); 
} 
 
void 
threadSafeValue::referTo(threadSafeValue& ValueToRefer){ 
    mutex=ValueToRefer.getMutex(); 
    mutex->enter(); 
    value.referTo(ValueToRefer.getUnsafeValue()); 
    mutex->exit(); 
} 
 
var 
threadSafeValue::getValue(){ 
    mutex->enter(); 
    var returnValue=value.getValue(); 
    mutex->exit(); 
    return returnValue; 
} 
 
void 
threadSafeValue::addListener(Value::Listener* newListener){ 
    value.addListener(newListener); 
} 
 
shared_ptr<CriticalSection>&  
threadSafeValue::getMutex(){ 
    return mutex; 
} 
 
Value& 
threadSafeValue::getUnsafeValue(){ 
    return value; 
} 
 
void 
threadSafeValue::removeListener(Value::Listener* Listener){ 
    value.removeListener(Listener); 
}

and the function where m_linesState is filled

void 
startingGrid::setParameters(map<string,int>& activedLinePin,juce::String& portname){ 
    for(int i=0; i<activedLinePin.size();++i){ 
        m_linesState.push_back(threadSafeValue(var(false))); 
        m_linesState[i].addListener(this); 
        m_signals.push_back(new Circle(0xffff0000)); 
        addChildComponent(m_signals[i]); 
    } 
    m_game.setParameters(activedLinePin, portname, m_linesState);
}

thank you very much for your help


#2

You shouldn’t be putting a Value (or in your case threadSafeValue) into a std::vector. You should really only put POD types/primitives into a std::vector as std::vector will use the assignment operator and copy constructor internally to shuffle around elements. When the copy constructor is used on a Value it creates a new Value but will drop it’s listeners.

You should either use an OwnedArray of Values (so the array will just contain pointers to values which are primitive types) or - even better - a ValueTree.


#3

Is that really true?

I thought copying a Value kept the original value and causes them to point to the same underlying issue (expected behaviour for a copy constructor), but drops the listeners (weird behaviour for a copy constructor and somewhat JUCE secret knowledge).

I don’t think std::vector is terribly inappropriate for any object with a reasonably efficient copy constructor… just happens that the Value object has this unusual behaviour for its listeners…


#4

Yes you are right. I’ve edited my answer accordingly.


#5

We should probably both be talking about the move constructor anyway now:)


#6

Now I guess the question is whether the move constructor should keep the listeners … :wink:


#7

Sorry, I don’t understand why you say it’s a costructor problem. The copy costructor is called before the listener is added and the problematic listener disappears when is constructed the second object of the vector. Or when a new element is pushed back each member of a vector is copied?


#8

Reading on cplusplus.com I’ve seen that if the size of the vector surpasses the capacity all the stored elements are reallocated, so that could explain why if I clear and refill the vector everything works fine, because his capacity has just been improved. Than could I solve it using vector::reserve before beginning to pushing back?


#9

You should not make assumptions on how std::vector may or may not construct elements. For example, as you say, it may pre-allocated elements and may decide to move around elements as it sees necessary. You should really not be using a std::vector with Values.


#10

Well the move constructor for some. Those still developing RTAS are still looking at the copy constructor :wink:


#11

I passed to OwnedArray and now it works, thank you very much


#12

You can say “No reallocation shall take place during insertions that happen after a call to reserve()
until the time when an insertion would make the size of the vector greater than the value of capacity()” (from the standard). So I don’t think it’s totally inappropriate to do it with a vector…although I’d agree you’d want to have a good reason for it, because if you made a mistake and expanded the vector the bugs would be subtle and annoying…