2017-02-08 4 views
2

J'ai une classe (A) avec ses constructeurs de copie supprimés (=delete;), déplace le constructeur et déplace l'assignation déclarée et définie. La classe contient une ressource qui ne doit pas être libérée deux fois, d'où la restriction de déplacement uniquement.Comment insérer un objet de déplacement uniquement dans une carte dans Visual C++?

J'ai une autre classe (B) qui contient une instance de A (pas un pointeur). J'ai écrit un opérateur d'affectation de mouvement pour la classe B qui appelle std::move() sur son membre de classe A.

Une carte contient plusieurs instances de B. Je crée une instance de B et affecte son membre A directement à l'affectation. Ensuite, j'essaie d'insérer B dans la carte. J'ai essayé operator[], insert et emplace de différentes manières, mais je continue d'obtenir une erreur affirmant que j'essaie d'utiliser une fonction supprimée avec la signature A(const A&). Donc, il essaie d'utiliser le constructeur de copie malgré qu'il soit supprimé?

Tout va bien jusqu'à ce que j'essaie de l'insérer dans la carte.

J'utilise VS2013.

Edit: Il est important pour mon projet (probablement sans rapport avec le problème en question) que la méthode que je finis à l'aide ne pas effectuer toute interaction bibliothèque avec le WinAPI (kernel32/ntdll etc.), de sorte que toutes les méthodes d'appel operator new() ou similaire en interne de toute sorte qui délègue à la WinAPI, je serai incapable d'utiliser. La carte a été pré-allouée avec reserve() pour essayer d'atténuer cela.

code (l'appel à emplace est ce qui provoque l'erreur de compilation):

/* main.cpp */ 

#include "smart_handle.h" 
#include <unordered_map> 
#include <Windows.h> 

struct MetadataStruct 
{ 
    MetadataStruct& operator=(MetadataStruct&& other) 
    { 
     if (this != &other) 
     { 
      handle = std::move(other.handle); 
      // Invalidate other.handle somehow... 
     } 
     return *this; 
    } 

    Util::SmartHandle handle; 
    // Also has other members. 
}; 

std::unordered_map<DWORD, MetadataStruct> metadataStructs; 

Util::SmartHandle GetHandle() 
{ 
    // Doing lots of stuff here to get the handle, then finally wraps 
    // it before returning, hence the need for its own function. 
    const HANDLE openedFileHandle = (HANDLE)-1; // Just an example handle. 
    return std::move(Util::SmartHandle(openedFileHandle, NULL)); 
} 

void F() 
{ 
    MetadataStruct metadataStruct; 
    metadataStruct.handle = GetHandle(); 

    metadataStructs.emplace(0, std::move(metadataStruct)); 
} 

int main(int argc, char** argv) 
{ 
    F(); 
    return 0; 
} 

/* smart_handle.h */ 
#pragma once 

#include <stdexcept> 
#include <Windows.h> 

namespace Util 
{ 
    class SmartHandle 
    { 
    public: 
     SmartHandle() = default; 
     SmartHandle(HANDLE handle, HANDLE invalidHandleValue); 

     SmartHandle(const SmartHandle& other) = delete; 
     SmartHandle& operator=(const SmartHandle& other) = delete; 

     SmartHandle(SmartHandle&& other); 
     SmartHandle& operator=(SmartHandle&& other); 

     ~SmartHandle(); 

     HANDLE GetValue() const; 
     bool IsValid() const; 
     void Close(); 
    private: 
     static const HANDLE uninitializedHandleValue; 

     HANDLE handle = uninitializedHandleValue; 
     HANDLE invalidHandleValue = uninitializedHandleValue; 

     void Set(HANDLE handle, HANDLE invalidHandleValue, bool throwIfInvalid = true); 
    }; 
} 

/* smart_handle.cpp */ 

#include "smart_handle.h" 

namespace Util 
{ 
    const HANDLE SmartHandle::uninitializedHandleValue = (HANDLE)-2; 

    SmartHandle::SmartHandle(const HANDLE handle, const HANDLE invalidHandleValue) 
    { 
     Set(handle, invalidHandleValue); 
    } 

    SmartHandle::SmartHandle(SmartHandle&& other) 
    { 
     handle = other.handle; 
     invalidHandleValue = other.invalidHandleValue; 

     other.handle = uninitializedHandleValue; 
     other.invalidHandleValue = uninitializedHandleValue; 
    } 

    SmartHandle& SmartHandle::operator=(SmartHandle&& other) 
    { 
     if (this != &other) 
     { 
      handle = other.handle; 
      invalidHandleValue = other.invalidHandleValue; 

      other.handle = uninitializedHandleValue; 
      other.invalidHandleValue = uninitializedHandleValue; 
     } 
     return *this; 
    } 

    SmartHandle::~SmartHandle() 
    { 
     Close(); 
    } 

    void SmartHandle::Set(const HANDLE handle, const HANDLE invalidHandleValue, const bool throwIfInvalid) 
    { 
     this->handle = handle; 
     this->invalidHandleValue = invalidHandleValue; 

     if (throwIfInvalid && !IsValid()) 
     { 
      this->handle = uninitializedHandleValue; 
      this->invalidHandleValue = uninitializedHandleValue; 
      throw std::invalid_argument("The handle used to initialize the object is not a valid handle"); 
     } 
    } 

    HANDLE SmartHandle::GetValue() const 
    { 
     if (handle == uninitializedHandleValue) 
     { 
      throw std::exception("Handle value not initialized"); 
     } 

     return handle; 
    } 

    bool SmartHandle::IsValid() const 
    { 
     return handle != uninitializedHandleValue && handle != invalidHandleValue; 
    } 

    void SmartHandle::Close() 
    { 
     const bool isPseudoHandle = handle == (HANDLE)-1; 

     if (IsValid() && !isPseudoHandle) 
     { 
      if (!CloseHandle(handle)) 
      { 
       throw std::exception("CloseHandle failed"); 
      } 

      handle = invalidHandleValue; 
     } 
    } 
} 
+0

Une note de côté: il existe des moyens plus simples pour gérer une ressource. Ils peuvent exiger un peu plus de code (pas beaucoup), mais vous ne devrez pas supporter d'objets non-copiables. La manière la plus simple (et celle qui nécessite encore moins de code): enveloppez votre ressource dans 'std :: shared_ptr'. Cela prendra de tout pour vous. –

Répondre

3

Vous pouvez utiliser emplace mais vous devez utiliser std::move conjointement avec elle afin de jeter l'objet que vous avez déjà dans un référence de référence. std::unique_ptr est mobile uniquement et vous pouvez le mettre dans une carte comme

int main() { 
    std::unique_ptr<int> foo; 
    std::map<int, std::unique_ptr<int>> bar; 
    bar.emplace(1, std::move(foo)); 
} 
+0

Mais 'unique_ptr' ne fonctionne qu'avec des pointeurs, n'est-ce pas? Lorsque j'essaie d'encapsuler l'objet A dans un pointeur intelligent, il me dit qu'il ne le supporte pas, mais si je préfixe la création de l'objet avec 'new', alors cela fonctionne. J'ai besoin de créer l'objet sur la pile, donc je ne peux pas utiliser 'new'. – Mikubyte

+0

@Mikubyte Le 'std :: unique_ptr' est sur la pile. Dans le code, je n'ai jamais utilisé 'new'. Ceci est juste un exemple puisque vous n'avez montré aucun code. Si vous mettez à jour votre question avec ce que vous avez réellement, je peux vous donner un bloc de code plus adapté. – NathanOliver

+0

Ajout d'un exemple de code qui capture l'essentiel de ce que j'essaie de faire. – Mikubyte