Edited at

shared_ptr を make_shared に何も考えずに置き換えようとして嵌った話

More than 1 year has passed since last update.


問題

まずは下記のコードを示します.


shared_ptrを使ったコード

#include <iostream>


class BaseEntity {
public:
BaseEntity() {
std::cout << "BaseEntity" << std::endl;
}
BaseEntity(const BaseEntity&) {
std::cout << "BaseEntity(const BaseEntity&)" << std::endl;
}
virtual ~BaseEntity() = default;
virtual int getvalue() const noexcept { return 100; }
};

class DerivedEntity : public BaseEntity {
public:
DerivedEntity() {
std::cout << "DerivedEntity" << std::endl;
}
DerivedEntity(const DerivedEntity&) {
std::cout << "DerivedEntity(const DerivedEntity&)" << std::endl;
}
virtual ~DerivedEntity() = default;
int getvalue() const noexcept override { return -100; }
};

int main()
{
auto p = std::shared_ptr<BaseEntity>(new DerivedEntity());
std::cout << "value: " << p->getvalue() << std::endl;
}


実行結果は下記です.


BaseEntity

DerivedEntity

value: -100


派生クラス DerivedEntity を new してるのでオーバーライドされた getvalue 関数は正しく -100 を返してくれました. 期待通りです.

まぁこれでも良いのですが, どうやらメモリ効率と安全性から make_shared を使った方が良いんだぞ, というプラクティスがあることを思い出し書き直そうと思いました(やや疲れ気味の頭で).


make_sharedを使った誤ったコード

#include <iostream>

#include <memory>

class BaseEntity {
public:
BaseEntity() {
std::cout << "BaseEntity" << std::endl;
}
BaseEntity(const BaseEntity&) {
std::cout << "BaseEntity(const BaseEntity&)" << std::endl;
}
virtual ~BaseEntity() = default;
virtual int getvalue() const noexcept { return 100; }
};

class DerivedEntity : public BaseEntity {
public:
DerivedEntity() {
std::cout << "DerivedEntity" << std::endl;
}
DerivedEntity(const DerivedEntity&) {
std::cout << "DerivedEntity(const DerivedEntity&)" << std::endl;
}
virtual ~DerivedEntity() = default;
int getvalue() const noexcept override { return -100; }
};

int main()
{
auto p = std::make_shared<BaseEntity>(DerivedEntity());
std::cout << "value: " << p->getvalue() << std::endl;
}



BaseEntity

DerivedEntity

BaseEntity(const BaseEntity&)

value: 100


眠い目をこすって何度も確認しても何故か 100 が返されているようにしか見えません.

「そうだ, きっとこれは夢なんだ」


何が不味かったか

もう一度下記を見てみましょう.


shared_ptrを使ったコード

auto p = std::shared_ptr<BaseEntity>(new DerivedEntity());



make_sharedを使った誤ったコード

auto p = std::make_shared<BaseEntity>(DerivedEntity());


DerivedEntity() で DerivedEntity がデフォルトコンストラクトされ, その後 BaseEntity のコピーコンストラクタに渡す, という動作になってしまっており, 意図しない有効なコード(コンパイラに怒られないという意味で)となってしまっていたのです.

派生クラスの shared_ptr を make_shared で作成するにはテンプレート引数として基底クラスではなく派生クラスを渡します.

なので, 下記のように記述すべきでした.

auto p = std::make_shared<DerivedEntity>();


参考

How to make_shared a derived class?