首页 > 解决方案 > 使用 pimpl idiom 移动操作

问题描述

在下面的代码中,我尝试在 PIMPL 惯用语中使用移动赋值,但代码无法编译。

结构.hpp:

#pragma once

#include <memory>

struct A {
  std::unique_ptr<struct B> m_x;
  A(int x);
  ~A();
};

结构.cpp:

#include "struct.hpp"

struct B {
  int x;
};

A::A(int x) : m_x{new B} { m_x->x = x; }
A::~A() = default;

主.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&)被删除了,因为它是为 a 删除的std::unique_ptr。但是为什么编译器首先尝试使用它呢?不应该std::move强制使用移动分配,它是有效的并为 a 定义的std::unique_ptr

标签: c++unique-ptrmove-semanticspimpl-idiom

解决方案


虽然std::unique_ptr确实有一个移动赋值运算符,并且想要利用这个事实来使移动赋值看起来很自然A,但用户声明的构造函数遇到了问题。

移动赋值运算符上的 cppreference :

隐式声明的移动赋值运算符

struct如果没有为类类型( 、class或)提供用户定义的移动赋值运算符union,并且以下所有情况都为真:

  • 没有用户声明的复制构造函数;
  • 没有用户声明的移动构造函数;
  • 没有用户声明的复制赋值运算符;
  • 没有用户声明的析构函数,

然后编译器将使用签名将移动赋值运算符声明inline为其类的公共成员T& T::operator=(T&&)

注意最后一个要点:A有一个用户声明的析构函数,所以你不会得到隐式声明的移动赋值运算符。

如果我们想以A最少的努力使移动赋值,我们可以显式声明移动赋值运算符并请求默认实现,如下所示:

结构.hpp:

#include <memory>

struct A {
  std::unique_ptr<struct B> m_x;
  A(int x);
  A& operator=(A&&) noexcept;
  ~A();
};

结构.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,隐式声明的移动赋值运算符将是。


推荐阅读