2024-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
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Wav2Lipのオープンソース版を改造して外部から呼べるAPI化する
Wav2Lipのオープンソース版で静止画の口元のみを動かして喋らせる
【iOS】アプリアイコン・ロゴ画像の作成・設定方法
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【2】
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【1】
【Xcode】iPhone is not available because it is unpairedの対処法
【Let's Encrypt】Failed authorization procedure 503の対処法
【Debian】古いバージョンでapt updateしたら404 not foundでエラーになる場合
ファイアウォール内部のWindows11 PCにmacOS Sequoiaからリモートデスクトップする
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
Windows11のコマンドプロンプトでテキストをコピーする
【Apache】サーバーに同時接続可能なクライアント数を調整する
GitLabにHTTPS経由でリポジトリをクローン&読み書きを行う
WindowsにSubversionをインストールしてGit Bashから使う
タスクスケジューラで変更を適用できません。ユーザーアカウントが不明であるか、パスワードが正しくないか、またはユーザーアカウントにタスクを変更する許可がありません。と出た
【Linux】iconv/libiconvをソースコードからインストール
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする