c ++ std :: unique_ptr不会在map中编译

时间:2014-01-11 00:21:09

标签: c++ visual-studio-2012 c++11 unique-ptr

我目前正在尝试将std :: unique_ptr存储在std :: unordered_map中,但是我得到了一个奇怪的编译错误。 相关代码:

#pragma once

#include "Entity.h"

#include <map>
#include <memory>

class EntityManager {
private:
    typedef std::unique_ptr<Entity> EntityPtr;
    typedef std::map<int, EntityPtr> EntityMap;

    EntityMap map;
public:

    /*
    Adds an Entity
    */
    void addEntity(EntityPtr);

    /*
    Removes an Entity by its ID
    */
    void removeEntity(int id) {
        map.erase(id);
    }

    Entity& getById(int id) {
        return *map[id];
    }
};

void EntityManager::addEntity(EntityPtr entity) {
    if (!entity.get()) {
        return;
    }

    map.insert(EntityMap::value_type(entity->getId(), std::move(entity)));
}

这是编译错误:

c:\program files (x86)\microsoft visual studio 12.0\vc\include\tuple(438): error C2280: 'std::unique_ptr<Entity,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : attempting to reference a deleted function
1>          with
1>          [
1>              _Ty=Entity
1>          ]
1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(1486) : see declaration of 'std::unique_ptr<Entity,std::default_delete<_Ty>>::unique_ptr'
1>          with
1>          [
1>              _Ty=Entity
1>          ]
1>          This diagnostic occurred in the compiler generated function 'std::pair<const _Kty,_Ty>::pair(const std::pair<const _Kty,_Ty> &)'
1>          with
1>          [
1>              _Kty=int
1>  ,            _Ty=EntityManager::EntityPtr
1>          ]

4 个答案:

答案 0 :(得分:7)

错误是因为代码中的某个地方,map想要复制std::pair<int, std::unique_ptr<Entity>>,但是没有能够复制的复制构造函数,因为unique_ptr不是可复制构造的。这是特别不可能防止多个指针拥有相同的内存。

所以在std :: move之前,没有办法使用不可复制的元素。

有一些解决方案here

但是,在c ++ 11中,Map可以使用std :: move来处理不可复制的值。

这是通过提供另一个插入操作符来完成的,该操作符被重载以包含此签名:

template< class P > std::pair<iterator,bool> insert( P&& value );

这意味着可以转换为value_type的类的rvalue可以用作参数。旧插件仍然可用:

std::pair<iterator,bool> insert( const value_type& value );

此插入实际上复制了一个value_type,这会导致错误,因为value_type不是可复制构造的。

我认为编译器正在选择非模板化的重载,这会导致编译错误。因为它不是模板,所以它的失败是一个错误。至少在gcc上,另一个使用std :: move的插入是有效的。

以下是测试代码,以确定您的编译器是否正确支持此功能:

#include <iostream>
#include <memory>
#include <utility>
#include <type_traits>

class Foo {
};

using namespace std;

int main() {
    cout << is_constructible<pair<const int,unique_ptr<Foo> >, pair<const int,unique_ptr<Foo> >& >::value << '\n';
    cout << is_constructible<pair<const int,unique_ptr<Foo> >, pair<const int,unique_ptr<Foo> >&& >::value << '\n';
}

第一行将输出0,因为复制构造无效。第二行将输出1,因为移动构造 有效。

此代码:

map.insert(std::move(EntityMap::value_type(entity->getId(), std::move(entity))));

应该调用move insert overload。

此代码:

map.insert<EntityMap::value_type>(EntityMap::value_type(entity->getId(), std::move(entity))));

真的应该叫它。

编辑:神秘感继续,vc为测试返回错误的11 ...

答案 1 :(得分:1)

您的代码使用以下内容:

int main() {
    EntityManager em;
    em.addEntity(std::unique_ptr<Entity>(new Entity(1)));

    return 0;
}

然而这很麻烦,我建议像这样定义addEntity:

void EntityManager::addEntity(Entity *entity) {
    if (entity == nullptr) 
        return;
    }

    map.insert(EntityMap::value_type(entity->getId(),
                std::unique_ptr<Entity>(entity)));
}

并插入

em.addEntity(new Entity(...));

答案 2 :(得分:0)

不确定此解决方案是否也可以为您提供帮助,但是当我从std::map<int, std::unique_ptr<SomeType>>中的静态库切换到动态库时,我突然在私有Visual Studio 2015 (Update 2)数据成员上遇到了同样的错误。< / p>

由于使用模板数据成员与__declspec(dllexport)一起产生警告(至少在MSVC中),我通过(几乎)应用PIMPL(私有实现)习语来解决该警告。 令人惊讶的是,C2280错误也以这种方式消失了。

在你的情况下,它将是:

class EntityManagerPrivate {
public:
    EntityMap map;
};

class EntityManager {
private:
    EntityManagerPrivate* d; // This may NOT be a std::unique_ptr if this class 
                             // shall be ready for being placed into a DLL
public:

    EntityManager();
    ~EntityManager();

   // ...
};

并在.cpp文件中:

EntityManager::EntityManager() :
    d( new EntityManagerPrivate() )
{
}

EntityManager::~EntityManager()
{
    delete d;
    d = nullptr;
}

// in all other methods, access map by d->map

请注意,对于真实的PIMPL,您必须将私有类移动到自己的头文件中,该文件仅由.cpp引用。在包含之后,实际标题只有前向声明class EntityManagerPrivate;。 对于真正的PIMPL,除了数据成员之外,私有类还必须具有实现。

答案 3 :(得分:0)

我在VS 2017上使用msvc 14.15.26726遇到了相同的问题。根据编译器错误日志,似乎与实例化期间对std :: pair <_kT,_T>的复制ctor的需求有关。我不知道为什么,但是对我来说,一个有趣的观察(和解决方法)是将std :: unique_ptr的声明放在地图的声明之前,例如:

#pragma once

#include "Entity.h"

#include <map>
#include <memory>

class EntityManager {
private:
    typedef std::unique_ptr<Entity> EntityPtr;
    typedef std::map<int, EntityPtr> EntityMap;
    std::unique_ptr<Entity> aDummyStub; //<-- add this line
    EntityMap map;
//...
};