为什么move构造函数既没有声明也没有用clang删除

Why is the move constructor neither declared nor deleted with clang?

本文关键字:有用 clang 删除 声明 move 构造函数 为什么      更新时间:2023-10-16

考虑以下类。

struct with_copy {
    with_copy() = default;
    with_copy(with_copy const&) {}
    with_copy& operator=(with_copy const&) { return *this; }
};
struct foo {
    with_copy c;
    std::unique_ptr<int> p;
};
  • with_copy有复制构造函数吗?对它是明确定义的
  • with_copy有移动构造函数吗?否。显式复制构造函数阻止生成它
  • with_copy是否有已删除的移动构造函数?不。没有move构造函数和删除了一个构造函数是不一样的。已删除的移动构造函数将尝试移动格式不正确的构造函数,而不是退化为副本
  • with_copy可复制吗?对它的复制构造函数用于复制
  • with_copy可移动吗?对它的复制构造函数用于移动

现在是棘手的问题。

  • foo有复制构造函数吗?对它有一个已删除的定义,因为它的默认定义会由于调用unique_ptr的已删除副本构造函数而格式不正确
  • foo有移动构造函数吗?GCC同意,clang拒绝
  • foo是否有已删除的移动构造函数?GCC和clang都拒绝了
  • foo可复制吗?否。它的复制构造函数已被删除
  • foo可移动吗?GCC同意,clang拒绝

(当考虑分配而不是施工时,行为是相似的。)

在我看来,GCC是正确的。foo应该有一个对每个成员执行移动的移动构造函数,在with_copy的情况下,它会退化为一个副本。Clang的行为似乎很荒谬:我有一个有两个可移动构件的骨料,但我的骨料是一块不可移动的砖。

谁是对的?

C++11,或者更确切地说是n3485,[class.copy]/9:

如果类X的定义没有显式声明移动构造函数,则会隐式声明一个当且仅当

  • CCD_ 15不具有用户声明的复制构造函数
  • CCD_ 16不具有用户声明的拷贝分配运算符
  • CCD_ 17不具有用户声明的移动分配运算符
  • X没有用户声明的析构函数,并且
  • move构造函数不会被隐式定义为已删除

和/11:

隐式声明的复制/移动构造函数是其类的inline public成员。默认副本/如果X具有,则类X的移动构造函数被定义为已删除(8.4.3)

  • […]
  • 对于复制构造函数,是右值引用类型的非静态数据成员,或者
  • 对于move构造函数,非静态数据成员或类型为没有move构造函数,并且不具有可复制性

由于with_copy不可复制的,foo将具有移动构造函数(它将被定义为已删除,因此不会隐式声明)。


C++1y,或者更确切地说,github repo从2013-11-12提交e31867c0;包含DR1402:

/9:

如果类X的定义没有显式声明移动构造函数,其中一个将被隐式声明为默认当且仅当如果

  • CCD_ 25不具有用户声明的复制构造函数
  • CCD_ 26不具有用户声明的拷贝分配运算符
  • X没有用户声明的移动分配运算符,并且
  • X没有用户声明的析构函数

和/11:

隐式声明的复制/移动构造函数是inline public其班级的成员。类X的默认复制/移动构造函数如果X具有,则定义为已删除(8.4.3)

  • […]
  • 对于复制构造函数,是右值引用类型的非静态数据成员

被定义为已删除的默认移动构造函数被忽略过载分辨率(13.3,13.4)。

这里,foo将有一个move构造函数。

我不太确定你测试了什么,但它foo肯定既可以移动赋值,也可以移动构造。诚然,这并没有说明移动构造函数或移动赋值是可访问的,只是右值的构造或赋值有效。clang(clang版本3.5(trunk 196718))和gcc(gcc版本4.9.0 20131031(实验性)(gcc))都同意这一评估。这是我尝试过的完整来源:

#include <iostream>
#include <type_traits>
#include <memory>
struct with_copy {
    with_copy() = default;
    with_copy(with_copy const&) {}
    with_copy& operator=(with_copy const&) { return *this; }
};
struct foo {
    with_copy c;
    std::unique_ptr<int> p;
};
int main()
{
    std::cout << "move constructible: "
              << std::is_move_constructible<foo>::value << 'n';
    std::cout << "move assignable: "
              << std::is_move_assignable<foo>::value << 'n';
    foo f0;
    foo f1 = std::move(f0);
    f0 = std::move(f1);
}