带有 pimpl 习语的移动操作
move operation with pimpl idiom
在下面的代码中,我试图在 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
定义的有效移动赋值吗?
虽然 std::unique_ptr
确实有一个移动赋值运算符,并且想要利用这一事实使 A
可移动赋值似乎很自然,但用户声明的构造函数遇到了问题。
move assignment operator 上的 cppreference:
Implicitly-declared move assignment operator
If no user-defined move assignment operators are provided for a class type (struct
, class
, or union
), and all of the following is true:
- there are no user-declared copy constructors;
- there are no user-declared move constructors;
- there are no user-declared copy assignment operators;
- there are no user-declared destructors,
then the compiler will declare a move assignment operator as an inline
public member of its class with the signature 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
,这是隐式声明的移动赋值运算符将是。
在下面的代码中,我试图在 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
定义的有效移动赋值吗?
虽然 std::unique_ptr
确实有一个移动赋值运算符,并且想要利用这一事实使 A
可移动赋值似乎很自然,但用户声明的构造函数遇到了问题。
move assignment operator 上的 cppreference:
Implicitly-declared move assignment operator
If no user-defined move assignment operators are provided for a class type (
struct
,class
, orunion
), and all of the following is true:
- there are no user-declared copy constructors;
- there are no user-declared move constructors;
- there are no user-declared copy assignment operators;
- there are no user-declared destructors,
then the compiler will declare a move assignment operator as an
inline
public member of its class with the signatureT& 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
,这是隐式声明的移动赋值运算符将是。