在下面的代码中,我试图在 PIMPL 惯用法中使用移动赋值,但代码无法编译。
struct.hpp:
#pragma once
#include <memory>
struct A {
std::unique_ptr<struct B> m_x;
A(int x);
~A();
};
struct.cpp:
#include "struct.hpp"
struct B {
int x;
};
A::A(int x) : m_x{new B} { m_x->x = x; }
A::~A() = default;
main.cpp:
#include <utility>
#include "struct.hpp"
int main()
{
A a(2);
A b(3);
a = std::move(b);
return 0;
}
虽然 struct.cpp
编译时没有警告,但 ```main.cpp`` 没有,给出错误:
$ g++ -c -std=c++17 -o main.o main.cpp
main.cpp: In function ‘int main()’:
main.cpp:8:18: error: use of deleted function ‘A& A::operator=(const A&)’
8 | a = std::move(b);
... (etc) ...
很明显,复制分配 A::operator=(const A&)
被删除,因为它是为 std::unique_ptr
删除的。
但是为什么编译器首先尝试使用它? std::move
不应该强制使用对 std::unique_ptr
有效且定义的移动赋值吗?
答案 0 :(得分:3)
虽然 std::unique_ptr
确实有一个移动赋值运算符,而且想要利用这一事实使 A
可移动赋值似乎很自然,但用户声明的构造函数遇到了问题。>
move assignment operator 上的 cppreference:
<块引用>隐式声明的移动赋值运算符
如果没有为类类型(struct
、class
或 union
)提供用户定义的移动赋值运算符,并且以下所有条件都为真:
然后编译器会将移动赋值运算符声明为其类的 inline
公共成员,其签名为 T& T::operator=(T&&)
。
注意最后一点:A
有一个用户声明的析构函数,所以你不会得到隐式声明的移动赋值运算符。
如果我们想以最少的努力使 A
可移动赋值,我们可以显式声明移动赋值运算符并请求默认实现如下:
struct.hpp:
#include <memory>
struct A {
std::unique_ptr<struct B> m_x;
A(int x);
A& operator=(A&&) noexcept;
~A();
};
struct.cpp:
#include "struct.hpp"
struct B {
int x;
};
A::A(int x) : m_x{ new B } { m_x->x = x; }
A::~A() = default;
A& A::operator=(A&&) noexcept = default;
我们需要在头文件中声明析构函数和移动赋值运算符,但推迟定义,直到源文件知道完全定义的 B
。请注意,我手动指定赋值运算符为 noexcept
,因为如果我在声明时未将其设为 default
,它将不会是 noexcept
,即隐式声明的移动赋值运算符是。