简体   繁体   English

c++ std::unique_ptr 不会在地图中编译

[英]c++ std::unique_ptr won't compile in map

I'm currently trying to store a std::unique_ptr in a std::unordered_map, but I get a weird compile error.我目前正在尝试将 std::unique_ptr 存储在 std::unordered_map 中,但出现奇怪的编译错误。 Relevant code:相关代码:

#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)));
}

This is the compile error:这是编译错误:

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>          ]

The error is because somewhere in the code, map wants to copy a std::pair<int, std::unique_ptr<Entity>> , however there is no copy constructor capable of this, because unique_ptr's are not copy constructable.错误是因为在代码中的某处, map 想要复制std::pair<int, std::unique_ptr<Entity>> ,但是没有能够做到这一点的复制构造函数,因为 unique_ptr 不可复制构造。 This is specifically impossible to prevent multiple pointers owning the same memory.这特别不可能防止多个指针拥有相同的内存。

So before std::move, there was no way to use an uncopiable element.所以在 std::move 之前,没有办法使用不可复制的元素。

There are some solutions here .有一些解决方案在这里

However, in c++11 Map can make use of std::move to work with non-copyable values.但是,在 c++11 中 Map 可以使用 std::move 来处理不可复制的值。

This is done by providing another insert operator, which is overloaded to include this signature:这是通过提供另一个插入运算符来完成的,该运算符被重载以包含此签名:

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

This means an rvalue of a class that can be turned into a value_type can be used as an argument.这意味着可以转换为 value_type 的类的右值可以用作参数。 The old insert is still available:旧的插入仍然可用:

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

This insert actually copies a value_type, which would cause an error since value_type is not copy constructable.这个插入实际上复制了一个 value_type,这会导致错误,因为 value_type 是不可复制构造的。

I think the compiler is selecting the non-templated overload, which causes the compilation error.我认为编译器正在选择非模板化的重载,这会导致编译错误。 Because it is not a template, it's failure is an error.因为它不是模板,所以它的失败是一个错误。 On gcc at least, the other insert, which uses std::move, is valid.至少在 gcc 上,另一个使用 std::move 的插入是有效的。

Here is test code to see if your compiler is supporting this correctly:这是测试代码,用于查看您的编译器是否正确支持此功能:

#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';
}

The first line will output 0, because copy construction is invalid.第一行将输出 0,因为复制构造无效。 The second line will output 1 since the move construction is valid.第二行输出1,因为移动结构有效的。

This code:这段代码:

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

should call the move insert overload.应该调用移动插入重载。

This code:这段代码:

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

Really should call it.真的应该叫它。

EDIT: the mystery continues, vc returns the incorrect 11 for the test...编辑:谜团还在继续,vc 为测试返回了不正确的 11 ......

Your code works with the following:您的代码适用于以下内容:

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

    return 0;
}

However this is cumbersome and I'd recommend defining addEntity like so:然而,这很麻烦,我建议像这样定义 addEntity:

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

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

and inserting with并插入

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

Not sure if this solution could help you as well, but I suddenly got the same error on a private std::map<int, std::unique_ptr<SomeType>> data member when I switched from a static library to a dynamic library in Visual Studio 2015 (Update 2) .不确定这个解决方案是否也能帮助你,但是当我从静态库切换到动态库时,我突然在私有std::map<int, std::unique_ptr<SomeType>>数据成员上遇到了同样的错误Visual Studio 2015 (Update 2)

Since using template data members together with __declspec(dllexport) produces a warning (at least in MSVC), I resolved that warning by (almost) applying the PIMPL (Private Implementation) idiom.由于将模板数据成员与__declspec(dllexport)一起使用会产生警告(至少在 MSVC 中),因此我通过(几乎)应用PIMPL (私有实现)习语解决了该警告。 Surprisingly, the C2280 error also disappeared that way.令人惊讶的是,C2280 错误也以这种方式消失了。

In your case, it would be:在您的情况下,它将是:

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();

   // ...
};

and in the .cpp file:并在 .cpp 文件中:

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

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

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

Note that for a real PIMPL you would have to move the private class into an own header file which is only referenced by the .cpp.请注意,对于真正的PIMPL您必须将私有类移动到仅由 .cpp 引用的自己的头文件中。 The actual header would only have a forward declaration class EntityManagerPrivate;实际的标头只有一个前向声明class EntityManagerPrivate; after the includes.在包含之后。 For a real PIMPL , the private class would also have to have implementation in addition to data members.对于真正的PIMPL ,私有类除了数据成员之外还必须有实现。

I had the same issue on VS 2017 with msvc 14.15.26726.我在使用 msvc 14.15.26726 的 VS 2017 上遇到了同样的问题。 According to the compiler error log, things seem to be related to the need for a copy ctor for std::pair<_kT, _T> during instantiation.根据编译器错误日志,事情似乎与在实例化期间需要 std::pair<_kT, _T> 的复制构造函数有关。 I don't know why, but one interesting observation (and workaround) for me is to put a declaration of a std::unique_ptr before the declaration of the map, eg:我不知道为什么,但对我来说一个有趣的观察(和解决方法)是在地图声明之前放置一个 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;
//...
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM