備忘録的にメモします。
#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時間ハマった...