C++でpimpl実装のクラスを継承したpimpl実装のクラスをコピーするとハマる

備忘録的にメモします。

```cpp
#include <iostream>
#include <memory>

class Base {
public:
  Base() : pimpl(new impl()) {}
  void dump() {std::cout<<"B: "<<pimpl<<std::endl;}
private:
  struct impl {}; std::shared_ptr<impl> pimpl;
};

class Derived : public Base {
public:
  Derived() : pimpl(new impl()) {}
  void dump() {Base::dump();std::cout<<"D: "<<pimpl<<std::endl;}
private:
  struct impl {}; std::shared_ptr<impl> pimpl;
};

int main() {
  Base b1;
  Base b2 = b1;

  b1.dump();
  b2.dump();

  Derived d1;
  Derived d2 = d1;

  d1.dump();
  d2.dump();

  return 0;
}
```

これを実行すると以下のようになります (例)

```
B: 0x7fd082404ce0
B: 0x7fd082404ce0
B: 0x7fd082404d10
D: 0x7fd082404d40
B: 0x7fd082404d10
D: 0x7fd082404d40
```

派生クラスのpimplがBaseもDerivedも同じ参照先を持っていますね。

このコードにRule of fiveを適用すると以下のようになります。

```cpp
#include <iostream>
#include <memory>

class Base {
public:
  Base() : pimpl(new impl()) {}
  Base(const Base& other) : pimpl(other.pimpl) {}
  Base(Base&& other) noexcept : pimpl(other.pimpl) {
    other.pimpl.reset();
  }
  Base& operator=(const Base& other) {
    Base tmp(other);
    *this = std::move(tmp);
    return *this;
  }
  Base& operator=(Base&& other) {
    swap(*this, other);
    return *this;
  }
  friend void swap(Base& a, Base& b) {
    std::swap(a.pimpl, b.pimpl);
  }
  void dump() {std::cout<<"B: "<<pimpl<<std::endl;}
private:
  struct impl {}; std::shared_ptr<impl> pimpl;
};

class Derived : public Base {
public:
  Derived() : pimpl(new impl()) {}
  Derived(const Derived& other) : pimpl(other.pimpl) {}
  Derived(Derived&& other) noexcept : pimpl(other.pimpl) {
    other.pimpl.reset();
  }
  Derived& operator=(const Derived& other) {
    Derived tmp(other);
    *this = std::move(tmp);
    return *this;
  }
  Derived& operator=(Derived&& other) {
    swap(*this, other);
    return *this;
  }
  friend void swap(Derived& a, Derived& b) {
    std::swap(a.pimpl, b.pimpl);
  }
  void dump() {Base::dump();std::cout<<"D: "<<pimpl<<std::endl;}
private:
  struct impl {}; std::shared_ptr<impl> pimpl;
};

int main() {
  Base b1;
  Base b2 = b1;

  b1.dump();
  b2.dump();

  Derived d1;
  Derived d2 = d1;

  d1.dump();
  d2.dump();

  return 0;
}
```

これを実行するともちろん最初のコードのようになって欲しいわけですが...

```
B: 0x7f9e49600000
B: 0x7f9e49600000
B: 0x7f9e49600030
D: 0x7f9e49600060
B: 0x7f9e49600090
D: 0x7f9e49600060
```

Baseのshared_ptrの参照先が違います。

考えてみれば当然ですがスーパークラスのコンストラクタを明示的に呼ばない場合は暗黙的に無引数のコンストラクタが呼ばれるので`Base(): pimpl(new impl) {}`が呼ばれてしまうためベースクラスのshared_ptrは新しい参照先を持ちます。

解決方法は自明ですね、スーパークラスのコンストラクタを明示的に呼びましょう。

(追記: swapもBaseのswapを呼ばないとダメですね、追加しました。)

```cpp
#include <iostream>
#include <memory>

class Base {
public:
  Base() : pimpl(new impl()) {}
  Base(const Base& other) : pimpl(other.pimpl) {}
  Base(Base&& other) noexcept : pimpl(other.pimpl) {
    other.pimpl.reset();
  }
  Base& operator=(const Base& other) {
    Base tmp(other);
    *this = std::move(tmp);
    return *this;
  }
  Base& operator=(Base&& other) {
    swap(*this, other);
    return *this;
  }
  friend void swap(Base& a, Base& b) {
    std::swap(a.pimpl, b.pimpl);
  }
  void dump() {std::cout<<"B: "<<pimpl<<std::endl;}
private:
  struct impl {}; std::shared_ptr<impl> pimpl;
};

class Derived : public Base {
public:
  Derived() : pimpl(new impl()) {}
  Derived(const Derived& other) : Base(other), pimpl(other.pimpl) {}
  Derived(Derived&& other) noexcept : Base(other), pimpl(other.pimpl) {
    other.pimpl.reset();
  }
  Derived& operator=(const Derived& other) {
    Derived tmp(other);
    *this = std::move(tmp);
    return *this;
  }
  Derived& operator=(Derived&& other) {
    swap(dynamic_cast<Base&>(*this), dynamic_cast<Base&>(other));
    swap(*this, other);
    return *this;
  }
  friend void swap(Derived& a, Derived& b) {
    std::swap(a.pimpl, b.pimpl);
  }
  void dump() {Base::dump();std::cout<<"D: "<<pimpl<<std::endl;}
private:
  struct impl {}; std::shared_ptr<impl> pimpl;
};

int main() {
  Base b1;
  Base b2 = b1;

  b1.dump();
  b2.dump();

  Derived d1;
  Derived d2 = d1;

  d1.dump();
  d2.dump();

  return 0;
}
```

出力結果

```
B: 0x7ff8c9c04ce0
B: 0x7ff8c9c04ce0
B: 0x7ff8c9c04d10
D: 0x7ff8c9c04d40
B: 0x7ff8c9c04d10
D: 0x7ff8c9c04d40
```

これのために5時間ハマった...