Move-Operation mit Pickel-Idiom

Move-Operation mit Pickel-Idiom

Die Lösung für die Bewegungsoperation mit dem Pimpl-Idiom
ist unten angegeben:

Im folgenden Code versuche ich, eine Bewegungszuweisung innerhalb des PIMPL-Idioms zu verwenden, aber der Code lässt sich nicht kompilieren.

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;
}

Während struct.cpp kompiliert ohne Warnung, „`main.cpp“ nicht und gibt den Fehler aus:

$ 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) ...

Es ist klar, dass die Kopieraufgabe A::operator=(const A&) wird gelöscht, weil es für std::unique_ptr gelöscht wird .
Aber warum versucht der Compiler überhaupt, es zu verwenden? Sollte nicht std::move sein Erzwingen Sie die Verwendung der Bewegungszuweisung, die für einen std::unique_ptr gültig und definiert ist ?

Während std::unique_ptr hat einen Zugzuweisungsoperator und es scheint natürlich, diese Tatsache nutzen zu wollen, um A zu machen move-assignable, stößt der vom Benutzer deklarierte Konstruktor auf Probleme.

cpreference auf den Verschiebezuweisungsoperator:

Beachten Sie den letzten Aufzählungspunkt:A hat einen vom Benutzer deklarierten Destruktor, sodass Sie den implizit deklarierten Move-Zuweisungsoperator nicht erhalten.

Wenn wir A machen wollen move-assignable mit minimalem Aufwand können wir den move-Zuweisungsoperator explizit deklarieren und die Standardimplementierung wie folgt anfordern:

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;

Wir müssen den Destruktor deklarieren und den Zuweisungsoperator in unserer Header-Datei verschieben, aber die Definition verschieben, bis die Quelldatei den vollständig definierten B kennt . Beachten Sie, dass ich manuell festlege, dass der Zuweisungsoperator noexcept ist , denn wenn ich es nicht schaffe default zum Zeitpunkt der Deklaration wird es nicht noexcept sein , was der implizit deklarierte move-Zuweisungsoperator wäre.