アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【C/C++
【C++】new/deleteを撤廃しstd::shared_ptrを使う
POSTED BY
2023-07-24

newで生成されるポインタはmallocと異なりプロセス内グローバルが保証されている訳ではないため、別クラス内でnewしたポインタを更に別のクラス内でdeleteするとSegmentation faultを起こすことがある。
応急対処としてあえてdeleteしないで落ちるのを回避などとしてしまうと、以後の開発に遺恨が残り泥沼にはまる。そんな意図はなくとも、delete自体を書くのを忘れてしまうこともある。

このような問題を解決するため、C++11以降ではnew/deleteでなくポインタクラス(スマートポインタ)を使うことが推奨されている。

まずは従来のnew/deleteを使用した最も一般的なサンプルを以下に示します。

C/C++new_delete.cppGitHub Source
#include <iostream>

class A {
public:
  A(void) {
    std::cout << "A constructor" << std::endl;
  }

  virtual ~A(void) {
    std::cout << "A destructor" << std::endl;
  }

  void hello(void) {
    std::cout << "A said こんにちは" << std::endl;
  }
};

class B {
public:
  A *mA;

  B(void) {
    std::cout << "B constructor" << std::endl;
    mA = new A();
  }

  A* getA(void){
    return mA;
  }

  virtual ~B(void) {
    std::cout << "B destructor" << std::endl;
    delete mA;
  }

  void hello(void) {
    std::cout << "B said こんにちは" << std::endl;
  }
};

class C {
public:
  B *mB;

  C(void) {
    std::cout << "C constructor" << std::endl;
    mB = new B;
  }

  B* getB(void){
    return mB;
  }

  virtual ~C(void) {
    std::cout << "C destructor" << std::endl;
    delete mB;
  }

  void hello(void) {
    std::cout << "C said こんにちは" << std::endl;
  }
};

int main(int argc, char **argv) {

  C *c = new C();
  c->hello();

  B *b = c->getB();
  b->hello();

  A *a = b->getA();
  a->hello();

  delete c;

  return 0;
}

それぞれのクラスが下位のクラスへのポインタをnewで持ち、デストラクタでdeleteして開放しています。mainでそれぞれのクラスへのポインタを取得して、hello関数を呼んで挨拶させます。
コンパイル、実行結果

g++ new_delete.cpp
./a.out

C constructor
B constructor
A constructor
C said こんにちは
B said こんにちは
A said こんにちは
C destructor
B destructor
A destructor

deleteし忘れなければ、特に問題はないC++の教科書的な挙動になります。ではnew/deleteを撤廃してポインタクラスを使用した全く同じ挙動の版を作成してみます。

C/C++shared_ptr.cppGitHub Source
#include <iostream>
#include <memory>

class A {
public:
  A(void) {
    std::cout << "A constructor" << std::endl;
  }

  virtual ~A(void) {
    std::cout << "A destructor" << std::endl;
  }

  void hello(void) {
    std::cout << "A said こんにちは" << std::endl;
  }
};

class B {
public:
  std::shared_ptr<A> mA;

  B(void) {
    std::cout << "B constructor" << std::endl;
    mA = std::make_shared<A>();
  }

  A& getA(void){
    return *(mA);
  }

  virtual ~B(void) {
    std::cout << "B destructor" << std::endl;
  }

  void hello(void) {
    std::cout << "B said こんにちは" << std::endl;
  }
};

class C {
public:
  std::shared_ptr<B> mB;

  C(void) {
    std::cout << "C constructor" << std::endl;
    mB = std::make_shared<B>();
  }

  B& getB(void){
    return *(mB);
  }

  virtual ~C(void) {
    std::cout << "C destructor" << std::endl;
  }

  void hello(void) {
    std::cout << "C said こんにちは" << std::endl;
  }
};

int main(int argc, char **argv) {

  std::shared_ptr<C> c = std::make_shared<C>();
  c->hello();

  B &b = c->getB();
  b.hello();

  A &a = b.getA();
  a.hello();

  return 0;
}

コンパイル、実行結果

g++ shared_ptr.cpp
./a.out

C constructor
B constructor
A constructor
C said こんにちは
B said こんにちは
A said こんにちは
C destructor
B destructor
A destructor

メンバ変数宣言。ポインタなのでこの時点ではAは生成されません。

std::shared_ptr<A> mA;

クラス生成。make_sharedで確保される。Aのコンストラクタもちゃんと呼ばれるし、引数も渡せる。

mA = std::make_shared<A>();

mAは生のポインタではなくポインタクラスであり内部で自動開放するため、deleteを呼ぶ必要はなくなる

A& getA(void){
   return *(mA);
}

*(mA)とすると、クラスAへの参照を返す。これが大きなポイント。さらに通常のポインタのようにアクセス演算子->も使える。

std::shared_ptr<C> c = std::make_shared<C>();
c->hello();

B &b = c->getB();
b.hello();

A &a = b.getA();
a.hello();

ポインタに対して直接helloを呼んでいるのでc->hello()でいいし、getBは*で参照を返しているのでb.hello()になる。

なお、クラス実体がNULLかどうか調べて処理分岐する場合従来では

if (mA) {
 ... //A実体がNULLでない(確保されている)なら処理
}

としていたが、shared_ptrを使う版では、

if (mA.get() != nullptr){
 ... //A実体がNULLでない(確保されている)なら処理
}

とする。

C++公式日本語リファレンス
https://cpprefjp.github.io/reference.html

C++公式日本語リファレンス/std::shared_ptr
https://cpprefjp.github.io/reference/memory/shared_ptr.html

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】