为什么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。
有两组规则适用于此:
移动构造函数和默认构造函数均未包含在 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.
现在适用自动生成非显式构造函数的规则(正如 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]).
在下面的代码中,派生 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。
有两组规则适用于此:
移动构造函数和默认构造函数均未包含在 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.
现在适用自动生成非显式构造函数的规则(正如 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]).