メインコンテンツまでスキップ

CPP

C++のポインタについて説明します。ポインタは非常に重要な概念ですので、基本から種類別の解説まで進めていきましょう。

ポインタの基本概念

ポインタとは、メモリ上の特定の場所(アドレス)を指し示す変数です。これにより、間接的にデータにアクセスすることができます。

int number = 10;     // 通常の変数
int* pointer = &number; // ポインタ変数(&演算子でnumberのアドレスを取得)

std::cout << "number の値: " << number << std::endl;
std::cout << "number のアドレス: " << &number << std::endl;
std::cout << "pointer の値(アドレス): " << pointer << std::endl;
std::cout << "pointer が指す値: " << *pointer << std::endl; // 間接参照演算子*

C++のポインタの種類

1. 生ポインタ(Raw Pointer)

C++の最も基本的なポインタです。メモリを直接管理する必要があり、メモリリークやダングリングポインタの原因になることがあります。

// 生ポインタの例
int* p = new int(42); // メモリ確保
*p = 100; // 値の変更
std::cout << *p << std::endl; // 100
delete p; // メモリ解放(忘れるとメモリリーク)
p = nullptr; // ポインタをnullに設定(良い習慣)

2. スマートポインタ(Smart Pointer)

C++11以降で導入された自動的にメモリ管理を行うポインタです。主に以下の種類があります:

a. unique_ptr

リソースの排他的な所有権を持つスマートポインタです。

#include <memory>

// unique_ptrの例
std::unique_ptr<int> up = std::make_unique<int>(42); // C++14以降
// std::unique_ptr<int> up(new int(42)); // C++11

std::cout << *up << std::endl; // 42
*up = 100;
std::cout << *up << std::endl; // 100

// 所有権の移動
std::unique_ptr<int> up2 = std::move(up); // upからup2へ所有権を移動
// std::cout << *up << std::endl; // エラー:upはもう所有権を持っていない
std::cout << *up2 << std::endl; // 100

// スコープを抜けるとメモリは自動的に解放される

b. shared_ptr

参照カウントによる共有所有権を持つスマートポインタです。

#include <memory>

// shared_ptrの例
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::cout << *sp1 << std::endl; // 42
std::cout << "参照カウント: " << sp1.use_count() << std::endl; // 1

{
std::shared_ptr<int> sp2 = sp1; // 共有
std::cout << "参照カウント: " << sp1.use_count() << std::endl; // 2
*sp2 = 100;
std::cout << *sp1 << std::endl; // 100(sp1とsp2は同じオブジェクトを指している)
} // sp2はスコープを抜け、参照カウントが減少

std::cout << "参照カウント: " << sp1.use_count() << std::endl; // 1
// 参照カウントが0になるとメモリは自動的に解放される

c. weak_ptr

循環参照を防ぐためのshared_ptrの補助ポインタです。

#include <memory>

// weak_ptrの例(循環参照の防止)
struct Person {
std::string name;
std::shared_ptr<Person> friend1;
std::weak_ptr<Person> friend2; // weak_ptrを使用

Person(const std::string& n) : name(n) {}
~Person() { std::cout << name << "のデストラクタが呼ばれました" << std::endl; }
};

{
auto person1 = std::make_shared<Person>("太郎");
auto person2 = std::make_shared<Person>("花子");

person1->friend1 = person2; // shared_ptrを使った相互参照
person2->friend1 = person1; // これは循環参照になる

// 以下のように weak_ptr を使うと循環参照が解決される
// person1->friend2 = person2; // weak_ptrを使用
// person2->friend2 = person1; // weak_ptrを使用
} // この時点でメモリリークが発生(循環参照のため)

3. その他のポインタ関連概念

a. const ポインタ

// constポインタの様々なパターン
int x = 10, y = 20;

int* p1 = &x; // 通常のポインタ
*p1 = 30; // OK: 値を変更可能
p1 = &y; // OK: ポインタ自体も変更可能

const int* p2 = &x; // ポインタが指す値をconstに
// *p2 = 30; // エラー: 値を変更できない
p2 = &y; // OK: ポインタ自体は変更可能

int* const p3 = &x; // ポインタ自体をconstに
*p3 = 30; // OK: 値を変更可能
// p3 = &y; // エラー: ポインタ自体を変更できない

const int* const p4 = &x; // 両方constに
// *p4 = 30; // エラー: 値を変更できない
// p4 = &y; // エラー: ポインタ自体を変更できない

b. void* ポインタ

型を指定しない汎用ポインタです。

// void*ポインタの例
int i = 10;
float f = 3.14f;
void* vp;

vp = &i; // intへのポインタをvoid*に代入
// std::cout << *vp << std::endl; // エラー: void*は直接参照できない
std::cout << *(static_cast<int*>(vp)) << std::endl; // キャストが必要

vp = &f; // floatへのポインタをvoid*に代入
std::cout << *(static_cast<float*>(vp)) << std::endl; // 3.14

c. nullptr

C++11で導入されたヌルポインタリテラルです。

// nullptrの例
int* p = nullptr;
// std::cout << *p << std::endl; // 危険: nullの参照外し(通常はクラッシュ)

if (p == nullptr) { // nullチェック
std::cout << "ポインタはnullです" << std::endl;
}

if (!p) { // 同等のチェック方法
std::cout << "ポインタはnullです" << std::endl;
}

ポインタの使用例と実践的なケース

以下は、ポインタを使った実践的なコード例です:

#include <iostream>
#include <memory>
#include <vector>

// 関数にポインタを渡す
void modifyValue(int* value) {
if (value) { // nullチェックは重要
*value *= 2;
}
}

// スマートポインタを使った関数
void processData(const std::shared_ptr<std::vector<int>>& data) {
for (auto& item : *data) {
item += 10;
}
}

int main() {
// 生ポインタの例
int num = 5;
modifyValue(&num);
std::cout << "修正後の値: " << num << std::endl; // 10

// スマートポインタの例
auto data = std::make_shared<std::vector<int>>(std::vector<int>{1, 2, 3});
std::cout << "処理前: ";
for (const auto& item : *data) {
std::cout << item << " ";
}
std::cout << std::endl;

processData(data);

std::cout << "処理後: ";
for (const auto& item : *data) {
std::cout << item << " ";
}
std::cout << std::endl;

return 0;
}

このように、C++におけるポインタには生ポインタとスマートポインタをはじめとする様々な種類があります。モダンC++では、メモリ管理の問題を避けるためにスマートポインタの使用が推奨されています。特にstd::unique_ptrstd::shared_ptrは多くのケースで生ポインタの代わりに使用できます。