1

In the following code the move constructor of the derived class is obviously not generated although the base class is move constructible.

#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;
}

With the above code I get an error (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);

The slice function in the derived class cannot return by value because the move constructor is missing. All other constructors seem to work as expected (not covered by this example).

If I define the move constructor explicitly (uncomment line) the example compiles. But in this case the default constructor vanishes which is, of course, not intended.

What is going on here? I do not understand either of the cases.

Why is the move constructor deleted in the first case?

Why is the default constructor dropped in the second case? Others seem to survive.

4

2 回答 2

4

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

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

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

  • X 没有用户声明的复制构造函数,

  • X 没有用户声明的复制赋值运算符

  • X 没有用户声明的移动赋值运算符,并且

  • X 没有用户声明的析构函数。


为什么在第二种情况下删除了默认构造函数?

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

...如果类 X 没有用户声明的构造函数,则没有参数的非显式构造函数被隐式声明为默认值 ([dcl.fct.def])。


此外,由于保证复制省略,此代码将在 C++17 之后工作。详细来说,在 C++17 之前,上下文的语义

return unique_num_array<T>(...);

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

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

于 2017-12-25T17:26:38.233 回答
1

这里有两组适用的规则:

  1. using 指令既不包含移动构造函数,也不包含默认构造函数。

    [...]不是默认构造函数或复制/移动构造函数并且其签名与派生类中用户定义的构造函数不匹配的所有候选继承构造函数都在派生类中隐式声明。

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

    如果类 X 的定义没有显式声明移动构造函数,当且仅[...] X 没有用户声明的复制赋值运算符时,非显式构造函数将被隐式声明为默认值

    [...]如果类 X 没有用户声明的构造函数,则没有参数的非显式构造函数被隐式声明为默认值([dcl.fct.def])。

于 2017-12-26T07:32:20.270 回答