为什么move构造函数不通过using声明继承

Why is the move constructor not inhereted by using declaration

在下面的代码中,派生 class 的移动构造函数显然没有生成,尽管基础 class 是可移动构造的。

#include <cstddef>
#include <memory>
#include <cstring>
#include <cassert>

template <typename T>
class unique_array : public std::unique_ptr<T[],void (*)(void*)>
{   size_t Size;
 protected:
    typedef std::unique_ptr<T[],void (*)(void*)> base;
    unique_array(T* ptr, size_t size, void (*deleter)(void*)) noexcept : base(ptr, deleter), Size(size) {}
 public:
    constexpr unique_array() noexcept : base(NULL, operator delete[]), Size(0) {}
    explicit unique_array(size_t size) : base(new T[size], operator delete[]), Size(size) {}
    unique_array(unique_array<T>&& r) : base(move(r)), Size(r.Size) { r.Size = 0; }
    void reset(size_t size = 0) { base::reset(size ? new T[size] : NULL); Size = size; }
    void swap(unique_array<T>&& other) noexcept { base::swap(other); std::swap(Size, other.Size); }
    size_t size() const noexcept { return Size; }
    T* begin() const noexcept { return base::get(); }
    T* end() const noexcept { return begin() + Size; }
    T& operator[](size_t i) const { assert(i < Size); return base::operator[](i); }
    unique_array<T> slice(size_t start, size_t count) const noexcept
    {   assert(start + count <= Size); return unique_array<T>(begin() + start, count, [](void*){}); }
};

template <typename T>
class unique_num_array : public unique_array<T>
{   static_assert(std::is_arithmetic<T>::value, "T must be arithmetic");
 public:
    using unique_array<T>::unique_array;
    unique_num_array(unique_num_array<T>&& r) : unique_array<T>(move(r)) {}
    unique_num_array<T> slice(size_t start, size_t count) const noexcept
    {   assert(start + count <= this->size()); return unique_num_array<T>(this->begin() + start, count, [](void*){}); }
 public: // math operations
    void clear() const { std::memset(this->begin(), 0, this->size() * sizeof(T)); }
    const unique_num_array<T>& operator =(const unique_num_array<T>& r) const { assert(this->size() == r.size()); memcpy(this->begin(), r.begin(), this->size() * sizeof(T)); return *this; }
    const unique_num_array<T>& operator +=(const unique_num_array<T>& r) const;
    // ...
};

int main()
{   // works
    unique_array<int> array1(7);
    unique_array<int> part1 = array1.slice(1,3);
    // does not work
    unique_num_array<int> array2(7);
    unique_num_array<int> part2 = array2.slice(1,3);
    // test for default constructor
    unique_num_array<int> array3;
    return 0;
}

使用上面的代码我得到一个错误(gcc 4.8.4):

test6.cpp: In function ‘int main()’: test6.cpp:47:48: error: use of deleted function ‘unique_num_array::unique_num_array(const unique_num_array&)’ unique_num_array part2 = array2.slice(1,3);

派生的 class 中的切片函数不能按值 return 因为缺少 移动构造函数 。所有其他构造函数似乎都按预期工作(此示例未涵盖)。

如果我显式定义移动构造函数(取消注释行),该示例将编译。但在这种情况下,默认构造函数消失了,这当然不是故意的。

这是怎么回事?这两种情况我都不明白。

为什么第一种情况删除了移动构造函数?

为什么在第二种情况下会丢弃默认构造函数?其他人似乎幸存下来。

Why is the move constructor deleted in the first case?

因为unique_num_array<T>中有一个用户声明的复制赋值运算符,所以编译器没有隐式声明任何移动构造函数。 [class.copy.ctor]/8 中的标准说

If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,

  • X does not have a user-declared copy assignment operator,

  • X does not have a user-declared move assignment operator, and

  • X does not have a user-declared destructor.


Why is the default constructor dropped in the second case?

因为unique_num_array<T>中有一个用户声明的移动构造函数,所以编译器没有隐式声明默认构造函数。 [class.ctor]/4 中的标准说

... If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted ([dcl.fct.def]).


另外,由于保证 copy elision,此代码将在 C++17 之后工作。详细地说,在 C++17 之前,上下文

语义
return unique_num_array<T>(...);

unique_num_array<int> part2 = array2.slice(1,3);

需要 copy/move 操作,而在 C++17 之后,语义变为目标对象由纯右值初始化程序初始化,而无需具体化临时对象,因此不需要 copy/move。

有两组规则适用于此:

  1. 移动构造函数和默认构造函数均未包含在 using 指令中。

    [...] All candidate inherited constructors that aren't the default constructor or the copy/move constructor and whose signatures do not match user-defined constructors in the derived class, are implicitly declared in the derived class.

  2. 现在适用自动生成非显式构造函数的规则(正如 xskxsr 已经提到的)。

    If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if [...] X does not have a user-declared copy assignment operator

    [...] If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted ([dcl.fct.def]).