LoginSignup
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-01-06

備忘録的にメモします。

#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を適用すると以下のようになります。

#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を呼ばないとダメですね、追加しました。)

#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時間ハマった...

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2