1

I am building an application with Qt5. My program builds and runs fine, but there is a collision between two threads accessing a data structure. I have a QList of CanMessage objects, and I want to protect some data inside of it using a QMutex. However, as soon as I add the QMutex to my class definition, I get errors:

QList.h: `error: C2280: 
'CanMessage::CanMessage(const CanMessage &)': attempting to reference a deleted function`. 

Here is my canmessage.h file:

#ifndef CANMESSAGE_H
#define CANMESSAGE_H

#include <QObject>
#include <QMutex>
#include "cansignal.h"

class CanMessage
{
public:
    CanMessage();
    /* snip - public function prototypes */

private:
    /* snip - private prototypes and data */
    QMutex m_messageMutex;
};

#endif // CANMESSAGE_H

And cansignal.h:

#ifndef CANSIGNAL_H
#define CANSIGNAL_H

#include <QObject>
#include <QDebug>
#include <QByteArray>

class CanSignal
{
public:
    CanSignal();

    CanSignal(QString &signalName, quint8 &signalLength, quint8 &signalStartBit,
                   float &offset, float &factor, bool &isBigEndian, bool &isFloat, bool &isSigned)
    {
        this->m_name = signalName;
        this->m_length = signalLength;
        this->m_startBit = signalStartBit;
        this->m_offset = offset;
        this->m_factor = factor;
        this->m_isBigEndian = isBigEndian;
        this->m_isFloat = isFloat;
        this->m_isSigned = isSigned;
    }

    bool setName(QString &signalName);
    bool setBitLength(quint8 &length);
    bool setStartBit(quint8 &startBit);
    bool setOffset(float &offset);
    bool setFactor(float &factor);
    bool setEndianess(bool &isBigEndian);
    bool setIsFloat(bool &isFloat);
    bool setIsSigned(bool &isSigned);
    void setValid();
    void setInvalid();
    void setEngineeringData(float data);

    QString getName();
    quint8 getBitLength();
    quint8 getStartBit();
    float getOffset();
    float getFactor();
    float getData();
    bool isBigEndian();
    bool isFloat();
    bool isSigned();
    bool getSignalValidity();


private:
    QString m_name;
    quint8 m_length;
    quint8 m_startBit;
    float m_offset;
    float m_factor;
    float m_data_float = 0;
    bool m_isBigEndian;
    bool m_isFloat;
    bool m_isSigned;
    // Set After everything in signal is filled
    bool m_isSignalValid = false;
};

#endif // CANSIGNAL_H
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Erik Johnson
  • 858
  • 1
  • 7
  • 29
  • can you give us the cansignal.h file ? – vcloarec Jun 27 '18 at 01:03
  • 2
    How is `CanMessage` being used? `QMutex` is not copyable, so `CanMessage` is also not copyable. For instance, if `CanMessage` is used in a container that requires copyable elements, this will not work. Your error message is referring to a deleted copy constructor, which makes sense. – Remy Lebeau Jun 27 '18 at 01:09
  • yup, see edits above – Erik Johnson Jun 27 '18 at 01:09
  • @RemyLebeau I have a QList of them. Is there a way to make that work? – Erik Johnson Jun 27 '18 at 01:10
  • Manually define a copy constructor for `CanMessage` that does not try to copy the `QMutex` object from one `CanMessage` to another. You might also need to dynamically allocate `m_messageMutex` at runtime so its construction does not affect `CanMessage`'s ability to be constructed. – Remy Lebeau Jun 27 '18 at 01:12
  • question is unclear if need to know why there's an error or how to make it work. – Joseph D. Jun 27 '18 at 01:16
  • @codekaizer I understand now why it is giving me the error, if someone could demonstrate how to make it work, it would be much appreciated – Erik Johnson Jun 27 '18 at 01:17
  • Erik, I've updated my answer to include a solution. It should be a good start :-) – paxdiablo Jun 27 '18 at 02:51

1 Answers1

2
CanMessage::CanMessage(const CanMessage &)

is the copy constructor, obviously being used to place an item into the list. That's not going to work since QMutex is not actually copyable.

How you solve it depends on a number of things. Perhaps the easiest method would be to modify CanMessage so that it has a dynamic mutex (created in the constructor, of course).

Then have a copy constructor for it that first locks the source object mutex then dynamically allocates a new mutex in the target object.

That way, you can guarantee the old object will be "clean" when copying (because you have its mutex) and there'll be no "trying to copy an uncopyable member" problem since the mutex itself is not copied. See footnote (a) for details.


The following code, which is a complete simple snippet showing the problem, compiles okay provided you leave the QMutex m_mutex; line commented out:

#include <QList>
#include <QMutex>

#include <iostream>

class Xyzzy {
private:
    int m_xyzzy;
    //QMutex m_mutex;
public:
    Xyzzy() : m_xyzzy(0) {};
    Xyzzy(int val) : m_xyzzy(val) {};
};

int main() {
    QList<Xyzzy> myList;
    Xyzzy plugh;
    myList.push_back(plugh);
    return 0;
}

Once you un-comment that line, the compiler rightly complains:

 error: use of deleted function 'Xyzzy::Xyzzy(const Xyzzy&)'

(a) In terms of fixing the problem, you could do something like:

#include <QList>
#include <QMutex>

#include <iostream>

class Xyzzy {
private:
    int m_xyzzy;
    QMutex *m_mutex; // Now a pointer
public:
    Xyzzy() : m_xyzzy(0) {
        m_mutex = new QMutex(); // Need to create in constructor.
        std::cout << "constructor " << m_mutex << '\n';
    };
    ~Xyzzy() {
        std::cout << "destructor " << m_mutex << '\n';
        delete m_mutex; // Need to delete in destructor.
    }
    Xyzzy(const Xyzzy &old) {
        old.m_mutex->lock();
        m_mutex = new QMutex(); // Need to make new one here.
        std::cout << "copy constructor from " << old.m_mutex
                  << " to " << m_mutex << '\n';
        old.m_mutex->unlock();
    }
};

int main() {
    QList<Xyzzy> myList;
    Xyzzy plugh;
    myList.push_back(plugh);
    return 0;
}

That one works properly, as per the following test run:

constructor 0x21c9e50
copy constructor from 0x21c9e50 to 0x2fff2f0
destructor 0x21c9e50
destructor 0x2fff2f0

In real code, I'd probably opt for smart pointers rather than raw new/delete calls but this is only meant to illustrate the concept. In addition, you'd need to handle all other possibilities which create a new object from an existing one as per the rule of three/five/whatever-comes-next, currently (from memory) limited to the copy assignment member Xyzzy &operator=(const Xyzzy &old).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953