【问题标题】:Presence of unordered_map determines whether copy-constructor or move constructor is usedunordered_map 的存在决定了是使用复制构造函数还是移动构造函数
【发布时间】:2019-03-09 01:47:55
【问题描述】:

在扩展一些预先存在的代码时,我遇到了涉及一些嵌套类和移动构造的情况,这会产生非常意外的行为。我最终能够提出两种可能的修复方法,但我不确定我是否完全理解了问题所在。

这里有一个小例子,其中一个类Foo 包含一个SubFoo 类型的字段和一个唯一指针,并且具有不同的复制和移动构造函数来反映唯一指针的所有权。请注意,有三个未定义的宏 --- 对应于代码的原始工作状态(即没有一个断言失败)。

#include <iostream>
#include <unordered_map>
#include <memory>
#include <vector>
#include <cassert>

//#define ADDMAP
//#define SUBFOO_MOVE
//#define FOO_MOVE_NONDEFAULT

class SubFoo {
public:
    SubFoo() {}
    SubFoo(const SubFoo& rhs) = default;
#ifdef SUBFOO_MOVE
    SubFoo(SubFoo&& rhs) noexcept = default;
#endif
private:
#ifdef ADDMAP
    std::unordered_map<uint32_t,uint32_t> _map;
#endif
};

class Foo {
public:
    Foo(const std::string& name, uint32_t data)
    : _name(name),
      _data(std::make_unique<uint32_t>(std::move(data))),
      _sub()
    {       
    }

    Foo(const Foo& rhs)
    : _name(rhs._name),
      _data(nullptr),
      _sub(rhs._sub)
    {
        std::cout << "\tCopying object " << rhs._name << std::endl;
    }

#ifdef FOO_MOVE_NONDEFAULT
    Foo(Foo&& rhs) noexcept
     : _name(std::move(rhs._name)),
       _data(std::move(rhs._data)),
       _sub(std::move(rhs._sub))
    {
        std::cout << "\tMoving object " << rhs._name << std::endl;
    }
#else
    Foo(Foo&& rhs) noexcept = default;
#endif

    std::string _name;
    std::unique_ptr<uint32_t> _data;
    SubFoo _sub;
};

using namespace std;
int main(int,char**) {
    std::vector<Foo> vec;

    /* Add elements to vector so that it has to resize/reallocate */
    cout << "ADDING PHASE" << endl;
    for (uint i = 0; i < 10; ++i) {
        std::cout << "Adding object " << i << std::endl; 
        vec.emplace_back(std::to_string(i),i);
    }
    cout << endl;

    cout << "CHECKING DATA..." << endl;
    for (uint i = 0; i < vec.size(); ++i) {
        const Foo& f = vec[i];
        assert(!(f._data.get() == nullptr || *f._data != i));
    }   
}

如上所述,这是代码的工作状态:由于元素被添加到向量中并且必须重新分配内存,因此调用了默认的移动构造函数而不是复制构造函数,正如“复制对象#" 永远不会打印,唯一的指针字段仍然有效。

然而,在向SubFoo(在我的例子中不是完全空的,但只包含更多基本类型)添加一个无序映射字段之后,在调整大小/重新分配向量时不再使用移动构造函数。 Here is a coliru link 可以在其中运行此代码,该代码启用了 ADDMAP 宏并导致断言失败,因为在向量调整大小期间调用了复制构造函数并且唯一指针变得无效。

我最终找到了两个解决方案:

  • SubFoo 添加默认移动构造函数
  • Foo 使用非默认移动构造函数,这看起来与我想象的默认移动构造函数完全一样。

您可以通过取消注释任何一个来在 coliru 中尝试这些 SUBFOO_MOVEFOO_MOVE_NONDEFAULT 宏。

然而,虽然我有一些粗略的猜测(见附记),但我大多感到困惑,并不真正理解为什么代码首先被破坏,也不明白为什么任何一个修复程序都修复了它。有人可以很好地解释这里发生了什么吗?

附:我想知道的一件事,虽然我可能偏离了轨道,但如果 SubFoo 中无序映射的存在以某种方式使 Foo 的移动构造不可行,为什么编译器不警告 = default 移动构造函数是不可能?

P.P.S.此外,虽然在这里显示的代码中我尽可能使用“noexcept”移动构造函数,但我对这是否可能存在一些编译器分歧。例如,clang 警告我,对于Foo(Foo&amp;&amp; rhs) noexcept = default,“错误:显式默认移动构造函数的异常规范与计算出的不匹配”。这和上面有关系吗?也许向量调整大小中使用的移动构造函数必须是 noexcept,而且不知何故我的不是真的......

除此之外进行编辑 这里可能存在一些编译器依赖性,但对于 coliru 使用的 g++ 版本,SubFoo 的(默认)移动构造函数 not 是否需要指定 noexcept 才能修复矢量大小调整问题(这与指定noexcept(false) 不同,后者不起作用):

non-noexcept SubFoo move ctor works

Foo 的自定义移动构造函数必须为 noexcept 才能修复:

non-noexcept Foo move ctor does not work

【问题讨论】:

  • 没错,unordered_map 的move constructor 不是noexcept,它解释了向量重新分配的行为。我相信,默认的特殊成员是否可以更改 noexceptness 会受到各种缺陷的影响,因此实施可能会因他们实施的缺陷解决方案而异。
  • @KerrekSB 那么,如果我手动指定一个 noexcept SubFoo 移动 ctor,编译器可能会将我的话作为其缺陷解决方案的一部分?还要注意我的编辑——我可以放弃 noexcept 规范并仍然解决问题还取决于编译器的实现这一事实吗?
  • 供应商可以自行决定添加noexcept。关于缺陷,我相信(但不确定)这是 wg21.link/cwg1778 以及最近采用的 wg21.link/p1286 的主题。

标签: c++ constructor move move-semantics


【解决方案1】:

有一个标准缺陷(在我看来)无序地图的移动 ctor 不是 noexcept。

因此,默认移动 ctor 为 noexcept(false) 或被您尝试的默认 noexcept(true) 删除似乎是合理的。

矢量调整大小需要一个 noexecept(true) 移动 ctor,因为它无法从第 372 个元素的移动投掷中理智而有效地恢复;它既不能回滚,也不能继续前进。它必须以某种方式丢失一堆元素而停止。

【讨论】:

  • unordered_maps 移动构造函数不能(无条件地)noexcept,因为它必须移动构造散列器、比较函子和分配器——这三者都可以由用户提供。
  • 问题:“[我的] 尝试默认 noexcept(true)”是什么意思(我假设是 SubFoo)——这不是一次成功的尝试吗?
  • FWIW,即使是用于 SubFoo 的 non-noexcept 移动 ctor 也可以(请参阅此处 coliru.stacked-crooked.com/a/ed13708b34e6dc01),而用于 Foo 的 non noexcept 移动 ctor不会 (coliru.stacked-crooked.com/a/88b41c152c16a853)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 2022-11-21
  • 2013-06-12
  • 1970-01-01
  • 2021-11-25
相关资源
最近更新 更多