【问题标题】:c++ std::unique_ptr won't compile in mapc++ std::unique_ptr 不会在地图中编译
【发布时间】:2014-01-30 04:00:55
【问题描述】:

我目前正在尝试将 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>          ]

【问题讨论】:

  • 使用 clang++ 3.5 编译良好​​span>
  • 我不知道编译时错误,但是这个调用:map.insert(EntityMap::value_type(entity-&gt;getId(), std::move(entity))) 无论如何都是不安全的,因为未指定将 entity 移动到函数的参数之前还是之后发生entity-&gt;getId()的评价。将getId() 分配给一个临时对象以传递给函数。
  • 这很可能是 MSVC 提供的 std 库的问题。查找错误报告,或设置ssccr.org 并提交一份?
  • 您可以尝试:map.insert(std::move(std::make_pair(entity-&gt;getId(), std::move(entity)))); 或尝试使用std::forward。我以前有过这个错误。我不记得我是如何在 VS2012 中解决它的,但我确定它涉及向前或移动。另一个选择是在地图中插入一个 nullptr。然后使用索引运算符,将分配移动到它。
  • 它通过右值引用传递,它将所有权从临时转移到地图。这就是移动构造函数的用途。例如:std::unique_ptr&lt;int&gt; up(new int); std::unique_ptr&lt;int&gt; up2(std::move(up)); int 指针的所有权从up 转移到up2

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


【解决方案1】:

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

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

有一些解决方案here

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

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

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

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

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

应该调用移动插入重载。

这段代码:

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

真的应该叫它。

编辑:谜团仍在继续,vc 为测试返回不正确的 11...

【讨论】:

  • 在带有-std=c++14 的Linux 上使用GCC,我不需要对std::move() 的外部调用。无论有没有对std::move() 的外部调用,它都有效。此外,您可能EntityMap::value_type 前面需要关键字typename(取决于您的模板层次结构)。如果是这样,请考虑使用定义本地typedef;有助于提高代码的可读性。
  • 我无法相信通过 Google 找到 C++ 代码示例是多么困难,这些示例演示了 std::unique_ptrstd::move()std::map 的作用。极好的。这个答案对于修复我的代码中的语法错误非常重要。
  • 这个答案的关键点:避免调用std::make_pair(),而是直接构造EntityMap::value_type的实例。
  • @kevinarpe map/set 和unique_ptr 等不可复制的问题在于,当它们用作这些容器中的键时,它们只能作为 const 访问,并且 c++ 中存在一些问题const 不可复制对象 不可能 删除(以非 ub 方式) - 如果它们在没有被编辑的情况下被移动(它们是 const),则将为容器中的对象调用一次析构函数, 一次用于移动的副本(相同,因为 const)。即一个智能指针会释放两次,等等。我问过那个here
  • @kevinarpe 我认为 make_pair 应该没问题,因为 temp 将作为 r 值绑定到通用引用 - 但我想如果你不将它包装在 move 中它可能会调用不同的重载跨度>
【解决方案2】:

您的代码适用于以下内容:

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

【讨论】:

  • 这就是我添加实体的方式。客户扩展实体。 entityManager.addEntity(std::unique_ptr&lt;Entity&gt;(new Customer(this, Vec2D(200, 200))));
  • @Tips48 知道它可能是什么,或者它的确切来源。您必须发布一个编译为您遇到的错误的示例。
  • 就是这样,它没有给出行号
  • 错误在于复制构造函数 - 尽管 unique_ptr 不可复制构造,但它在代码中的某处被调用 - 因此引用“已删除”函数(复制构造函数)
  • 我没有说编译器/平台可用,但是我假设 Ty 是值类型,KTy 是键类型,所以当某些函数返回一对键和值时,它是调用复制构造函数(你自己调用创建这样一个对是有效的,因为你使用了 std::move)——让我考虑一下......
【解决方案3】:

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

由于将模板数据成员与__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,除了数据成员之外,私有类还必须具有实现。

【讨论】:

    【解决方案4】:

    我在使用 msvc 14.15.26726 的 VS 2017 上遇到了同样的问题。根据编译器错误日志,事情似乎与实例化期间需要为 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;
    //...
    };
    

    【讨论】:

      猜你喜欢
      • 2014-05-12
      • 2015-07-23
      • 2019-11-17
      • 2013-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-07
      相关资源
      最近更新 更多