【问题标题】:Why is the move constructor not inhereted by using declaration为什么使用声明不继承移动构造函数
【发布时间】:2018-06-06 17:01:42
【问题描述】:

在下面的代码中,虽然基类是可移动构造的,但显然没有生成派生类的移动构造函数。

#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:在函数“int main()”中:test6.cpp:47:48:错误:使用 删除函数‘unique_num_array::unique_num_array(const unique_num_array&)’ unique_num_array part2 = array2.slice(1,3);

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

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

这里发生了什么?这两种情况我都不明白。

为什么第一种情况下move构造函数被删除了?

为什么在第二种情况下删除了默认构造函数?其他人似乎活了下来。

【问题讨论】:

  • Compiles with g++ 5.4.0,物有所值。
  • 你应该更喜欢组合而不是继承。
  • 您可以考虑使用std::vector
  • @Jarod42:我正在重构旧代码,其中包含一些资源泄漏和其他不那么漂亮的东西。事实上,还有另一个继承自 unique_num_array 的类,它为快速数字运算库分配特殊内存。 std::vector 可以使用自定义分配器来做到这一点,但它的缺点是它可调整大小和可复制。在这种情况下,两者都不是。而且 AFAIK 标准仍然没有提供大小不可变的容器。

标签: c++ c++11 constructor using-declaration


【解决方案1】:

为什么第一种情况下move构造函数被删除了?

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

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

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

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

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

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


为什么在第二种情况下会丢弃默认构造函数?

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

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


此外,由于保证copy elision,此代码将在 C++17 之后工作。详细来说,在 C++17 之前,语义两者的上下文

return unique_num_array<T>(...);

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

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

【讨论】:

  • 缺少一个重要部分,但您的回答绝对让我朝着正确的方向前进。 using 指令不处理所有构造函数(如我所料)。
【解决方案2】:

这里有两组适用的规则:

  1. 移动构造函数和默认构造函数都没有被 using 指令覆盖。

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

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

    如果类 X 的定义没有显式声明移动构造函数,当且仅 if [...] X 没有用户声明的复制赋值运算符

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-05
    • 2020-02-28
    • 2013-08-27
    • 1970-01-01
    • 2013-02-27
    • 2018-07-03
    • 2014-03-08
    • 1970-01-01
    相关资源
    最近更新 更多