【问题标题】:Why does `std::pair<int, movable>` require a [deleted] `const&` copy constructor?为什么 `std::pair<int,movable>` 需要 [deleted] `const` 复制构造函数?
【发布时间】:2014-12-23 19:39:52
【问题描述】:

我正忙于测试各种通用算法的实现,并且我正在使用对所提供函数的支持最少的类型。我在使用带有某种类型 T(例如 int)的 std::pair&lt;T, movable&gt; 和定义如下的 movable 类型时遇到了这种奇怪的设置:

struct movable
{
    movable() {}
    movable(movable&&) = default;
    // movable(movable const&) = delete;
    movable(movable&) = delete;
};

这个想法是有一个可移动但不可复制的类型。这很好用,例如,使用这样的表达式:

movable m1 = movable();
movable m2 = std::move(m1);

但是,当尝试将此类型用作std::pair&lt;...&gt; 的成员时,它会失败!要编译代码,必须添加 deleted(!) 复制构造函数,采用 movable const&amp;(或只有该版本)。采用非const 引用的复制构造函数不足:

#include <utility>
auto f() -> std::pair<int, movable> {
    return std::pair<int, movable>(int(), movable());
}

这里发生了什么?通过强制将 std::pair(std::pair const&amp;) 指定为 = defaulted 是否过度指定了 std::pair&lt;...&gt;?

问题似乎归结于std::pair 的复制构造函数的规范(在 20.3.2 [pairs.pair] 概要中):

 namespace std {
     template <class T1, class T2>
     struct pair {
         ...
         pair(const pair&) = default;
         ...
     };
 }

快速检查我的实现意味着复制两个成员的明显实现不需要const&amp; 版本的movable 复制构造函数。也就是说,冒犯的部分是pair的复制构造函数上的= default!

【问题讨论】:

  • @Borgleader:我同意它是相关的,但这个问题并没有解决 = default 被强制成为问题的问题! ...或者,就此而言,为什么使用= default 实际上会导致这个问题。该问题的拟议解决方案还保留了冒犯性的pair(pair const&amp;) = default。
  • @Dietmar:我对那个问题/答案的阅读表明=default 根本不是问题。除非使用,否则不应实例化。
  • 哦..又是一年中的那个时候,当您开始发布有趣的问题时? :D
  • 它似乎还没有被引用:[dcl.fct.def.default]/1.2 说明了为什么 pair 的默认复制 ctor 是非法的 - 我将其解释为 它的声明格式不正确,因此不需要实例化来使程序格式错误。

标签: c++ c++11 stl c++-standard-library


【解决方案1】:

std::pair复制构造函数声明如下:

pair(const pair&) = default;

通过为movable声明这个复制构造函数:

movable(movable&) = delete;

您禁止隐式创建 movable(const movable&amp;)(因此它甚至没有被删除,只是没有这样的构造函数),因此这是您拥有的唯一复制构造函数。但是std::pair 复制构造函数需要其成员的复制构造函数来获取 const 引用,因此会出现编译错误。

如果你添加这个:

movable(movable const&) = delete;

或者(更好)只是删除 movable(movable&amp;) = delete; 声明,您现在拥有 movable(movable const&amp;) 构造函数,并且因为它已被删除,所以 std::pair 复制构造函数也被删除。

更新:让我们考虑一个更简单的示例来演示相同的问题。这不编译:

template <typename T>
struct holder {
    T t;
    // will compile if you comment the next line
    holder(holder const&) = default;
    // adding or removing move constructor changes nothing WRT compile errors
    // holder(holder&&) = default;
};

struct movable {
    movable() {}
    movable(movable&&) = default;
    // will also compile if you uncomment the next line
    //movable(movable const&) = delete;
    movable(movable&) = delete;
};

holder<movable> h{movable()};

如果你注释holder的复制构造函数,它将编译,因为这是隐式复制构造函数生成的工作原理([class.copy]/8:

类 X 的隐式声明的复制构造函数将具有以下形式

X::X(const X&amp;)

如果类类型M(或其数组)的每个可能构造的子对象都有一个复制构造函数,其第一个参数的类型为const M&amp; 或const volatile M&amp;。否则,隐式声明的复制构造函数将具有以下形式

X::X(X&amp;)

也就是说,当您注释掉声明 holder(holder const&amp;) = default; 时,holder 的隐式声明的复制构造函数将具有 holder(holder&amp;) 的形式。但如果你不这样做,T 的复制构造函数将采用const T&amp;(或const volatile T&amp;),因为这将在[class.copy]/15 中描述的成员复制过程中调用。

如果holder 有一个移动构造函数,那就更简单了——如果你注释掉holder(holder const&amp;) = default;,隐式声明的holder 的复制构造函数将被删除。

【讨论】:

  • 好吧,我了解这么多——除了我的代码显然从未真正复制这对!它只会移动这对。 ...正如在添加 = deleted 复制构造函数导致它编译时编译的代码所示。另外,用明显的实现定义std::pair 的复制构造函数而不是使用=default 使代码编译!
  • 为什么需要这样做?使用显而易见的方法实现pair 的复制构造函数消除了这种需要。 = default 版本的行为不应该相同吗?
  • 好吧,您的holder 显然不是可移动构造的,因为它声明了一个复制构造函数,从而禁止了自动创建的移动构造函数,但也没有声明移动构造。 std::pair 定义了一个移动构造函数(它也是= defaulted)。我仍然不想复制我的对象,只想移动它。
  • 问题似乎是@dyp 指出的:如果默认构造函数的声明与隐式声明的不同(添加const 除外),则默认构造函数的声明格式不正确,独立于无论是否使用。
  • 好吧,对于只移动类型,明确禁用复制构造是有意义的。当然,它可以使用movable(movable const&amp;) 完成,但由于不能使用此构造函数,因此任何变体应该都可以工作。我可以清楚地解决 my 代码的问题,但创建库我希望对使用该库的用户友好。如果 std::pair 不会被定义为具有 = defaulted 复制构造函数,则两个版本都可以工作(并且标准库默认复制构造函数是错误的)。
猜你喜欢
  • 2017-03-26
  • 2018-03-14
  • 1970-01-01
  • 2014-04-16
  • 1970-01-01
  • 1970-01-01
  • 2012-09-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多