2023-07-23
newで生成されるポインタはmallocと異なりプロセス内グローバルが保証されている訳ではないため、別クラス内でnewしたポインタを更に別のクラス内でdeleteするとSegmentation faultを起こすことがある。
応急対処としてあえてdeleteしないで落ちるのを回避などとしてしまうと、以後の開発に遺恨が残り泥沼にはまる。そんな意図はなくとも、delete自体を書くのを忘れてしまうこともある。
このような問題を解決するため、C++11以降ではnew/deleteでなくポインタクラス(スマートポインタ)を使うことが推奨されている。
まずは従来のnew/deleteを使用した最も一般的なサンプルを以下に示します。
C/C++ | new_delete.cpp | GitHub 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.cpp | GitHub 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; </a>
クラス生成。make_sharedで確保される。Aのコンストラクタもちゃんと呼ばれるし、引数も渡せる。
mA = std::make_shared<a>(); </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(); </c></c>
ポインタに対して直接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
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Windowsのデスクトップ画面をそのまま配信するための下準備
WindowsでGPUの状態を確認するには(ASUS系監視ソフトの自動起動を停止する)
CORESERVER v1プランからさくらインターネットスタンダートプランへ引っ越しメモ
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
【Apache】サーバーに同時接続可能なクライアント数を調整する
GitLabにHTTPS経由でリポジトリをクローン&読み書きを行う
Windows版Google Driveが使用中と言われアンインストールできない場合
【Linux】iconv/libiconvをソースコードからインストール
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
【PHP】Mail/mimeDecodeを使ってメールの中身を解析(準備編)
Googleファミリーリンクで子供の端末の現在地がエラーで取得できない場合