2017-09-24 7 views
2

Supposons que j'ai une classe:Est-ce que le constructeur move a un sens pour une classe dont les champs std :: thread's et std :: mutex sont?

class WonnaBeMovedClass 
{ 
public: 

    WonnaBeMovedClass(WonnaBeMovedClass&& other); 

    void start(); 

private: 

    void updateSharedData(); 

    std::vector<int> _sharedData; 
    std::thread _threadUpdate; 
    std::mutex _mutexShaderData; 

    //other stuff 
}; 

WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other) 
{ 
    _sharedData = std::move(other._sharedData); 
    _threadUpdate = std::move(other._threadUpdate); 
    _mutexShaderData = std::move(other._mutexShaderData); //won't compile. 
} 

void WonnaBeMovedClass::start() 
{ 
    _threadUpdate = std::thread(&updateSharedData, this); 
} 

void WonnaBeMovedClass::updateSharedData() 
{ 
    std::lock_guard<std::mutex> lockSharedData(_mutexShaderData); 

    for (auto& value : _sharedData) 
    { 
     ++value; 
    } 
} 

Il ne compilera pas parce que mutex ne peut être déplacé. Cela n'a pas de sens. Je pensais qu'il est possible de contourner cela en utilisant des pointeurs au lieu des variables réelles et est venu avec les éléments suivants:

class WonnaBeMovedClass 
{ 
public: 

    WonnaBeMovedClass(WonnaBeMovedClass&& other); 

    void start(); 

private: 

    void updateSharedData(); 

    std::vector<int> _sharedData; 
    std::unique_ptr<std::thread> _threadUpdate //pointer; 
    std::unique_ptr<std::mutex> _mutexShaderData //pointer; 

    //other stuff 
}; 

WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other) 
{ 
    _sharedData = std::move(other._sharedData); 
    _threadUpdate = std::move(other._threadUpdate); 
    _mutexShaderData = std::move(other._mutexShaderData); //won't compile. 
} 

void WonnaBeMovedClass::start() 
{ 
    _threadUpdate = std::make_unique<std::thread>(&updateSharedData, this); 
} 

void WonnaBeMovedClass::updateSharedData() 
{ 
    std::lock_guard<std::mutex> lockSharedData(*_mutexShaderData); 

    for (auto& value : _sharedData) 
    { 
     ++value; 
    } 
} 

Alors maintenant, quand je:

WonnaBeMovedClass object1; 
WonnaBeMovedClass object2; 

//do stuff 

object1 = std::move(object2); 

Je propose en fait les adresses des deux mutex et le fil. Ça a plus de sens maintenant ... Ou pas?

Le thread travaille toujours avec les données de object1, pas object2, donc cela n'a toujours aucun sens. J'ai peut-être déplacé le mutex, mais le thread n'est pas conscient de object2. Ou est-ce? Je suis incapable de trouver la réponse et je vous demande de l'aide. Est-ce que je fais quelque chose de complètement faux et copier/déplacer des threads et des mutex est juste un mauvais design et je devrais repenser l'architecture du programme?

Edit:

Il y avait une question sur le but réel de la classe. Il s'agit en fait d'un client TCP/IP (représenté en tant que classe) qui contient:

  • dernières données du serveur (plusieurs tables de données, similaires à std :: vector).
  • contient des méthodes qui gèrent les unités d'exécution (état de mise à jour, envoi/réception de messages).

Plus d'une connexion peut être établie à la fois, donc quelque part dans un code il y a un champ std::vector<Client> qui représente toutes les connexions actives. Les connexions sont déterminées par le fichier de configuration.

//read configurations 

... 

//init clients 
for (auto& configuration : _configurations) 
{ 
    Client client(configuration); 
    _activeClients.push_back(client); // this is where compiler reminded me that I am unable to move my object (aka WonnaBeMovedClass object). 
}} 

J'ai changé _activeClientsstd::vector<Client>-std::vector<std::unique_ptr<Client>> et modifié le code d'initialisation pour créer des objets pointeur au lieu d'objets directement et travaillé autour de mes questions, mais la question reste donc je décidé de poster ici.

+0

votre approche avec le pointeur ne compilera pas non plus, ou est votre commentaire un reste? – gsamaras

+0

en double de https://stackoverflow.com/questions/7557179/move-constructor-for-stdmutex? –

+0

Vous pouvez créer un wrapper de style PImpl pour le premier cas. La seconde approche peut également fonctionner, mais vous devrez créer '_mutexShaderData'. – VTT

Répondre

1

Résolvez le problème en deux.

  1. Déplacement de mutex. Cela ne peut pas être fait parce que les mutex sont normalement implémentés en termes d'objets OS qui doivent avoir des adresses fixes. En d'autres termes, le système d'exploitation (ou la bibliothèque d'exécution, qui est la même que le système d'exploitation à nos fins) conserve une adresse de votre mutex. Cela peut être contourné en stockant des pointeurs (intelligents) sur les mutex dans votre code, et en les déplaçant à la place. Le mutex lui-même ne bouge pas. Les objets Thread peuvent être déplacés de sorte qu'il n'y a pas de problème.
  2. Déplacer vos propres données alors que du code actif (un thread ou une fonction en cours d'exécution ou un std::function stocké quelque part ou autre) a l'adresse de vos données et peut y accéder. C'est en fait très similaire au cas précédent, seulement à la place du système d'exploitation c'est votre propre code qui tient sur les données. La solution, comme précédemment, consiste à ne pas déplacer vos données. Stockez et déplacez un pointeur (intelligent) vers les données à la place.

En résumé,

class WonnaBeMovedClass 
{ 
public: 
    WonnaBeMovedClass 
     (WonnaBeMovedClass&& other); 
    void start(); 
private: 
    struct tdata { 
     std::vector<int> _sharedData; 
     std::thread _threadUpdate; 
     std::mutex _mutexShaderData; 
    }; 
    std::shared_ptr<tdata> data; 
    static void updateSharedData(std::shared_ptr<tdata>); 
}; 

void WonnaBeMovedClass::start() 
{ 
    _threadUpdate = std::thread(&updateSharedData, data); 
} 
0

WonnaBeMovedClass est un handle contenant thread et mutex, ce n'est donc pas une mauvaise conception de leur fournir une sémantique de mouvement (mais pas de copier).

La deuxième solution semble correcte, mais n'oubliez pas la gestion correcte des ressources pour votre mutex (construction et destruction). Je ne comprends pas vraiment le but réel de la classe, donc en fonction de la solution entière, il peut être préférable d'utiliser shared_ptr au lieu de unique_ptr (dans le cas où plusieurs WonnaBeMovedClass peuvent partager le même mutex).

std::thread est lui-même une poignée de fil du système, il ne doit pas être enveloppé dans un pointeur, la gestion des ressources (i.e. OS poignée de fil) est géré par la bibliothèque standard lui-même.

Notez que mutex sont en fait des objets noyau (habituellement comme un pointeur implémentés opaque, par exemple dans Windows API), et donc ne doit pas être modifié ou changé par code utilisateur de toute façon.

+0

La deuxième solution n'est pas bonne, car le thread stocke un pointeur sur l'objet qui le possède (voir la fonction 'start'). Lorsqu'il est déplacé, ce pointeur n'est pas ajusté et pointe toujours vers l'objet déplacé. –

+0

Oui, j'ai aussi pensé cela. – CorellianAle

0

Cela a plus de sens maintenant ... Ou pas?

Pas vraiment.

Si un std::mutex est déplacé, les autres threads ne seront pas au courant de la modification de l'adresse mémoire de ce mutex! Cela supprime la sécurité du filetage.


Cependant, une solution avec std::unique_ptr existe dans Copy or Move Constructor for a class with a member std::mutex (or other non-copyable object)?

dernier, mais pas moins C++ 14 semble avoir quelque chose à apporter dans le jeu. Lire la suite dans How should I deal with mutexes in movable types in C++?