C++
エラー
C2440
これは型が不一致の場合に起こるエラーである。
wchar_t* wstr = L"Helloworld";
これを実行すると、
'初期化中': 'const wchar_t [11]' から 'wchar_t *' に変換できません。
というエラーメッセージが表示される。
これは、L"Helloworld"の型がconst wchar_tで、wchar_t *の型に代入しようとしているためにエラーが起きている。
C2280
以下のようなvectorを作成し、ビルドした際に発生した
std::vector<std::unique_ptr<int>> test;
std::unique_ptrでさらに囲むとエラーは解決した。
std::unique_ptr<std::vector<std::unique_ptr<int>>> test;
deleteの後にnullptrを代入する理由
ダングリングポインタを防ぐためこれが行われる。
ダングリングポインタのパターン
解放後のポインタの使用
int* ptr = new int(10);
delete ptr; // メモリを解放
*ptr = 20; // 未定義動作(解放済みメモリにアクセス)
スコープを抜けたローカル変数の参照
int* getPointer() {
int localVar = 42;
return &localVar; // スコープを抜けるとlocalVarは無効になる
}
int* p = getPointer();
std::cout << *p; // 未定義動作(無効なメモリにアクセス)
メモリの二重解放
int* ptr = new int(5);
delete ptr;
delete ptr; // 二重解放(未定義動作)
配列の解放後のアクセス
int* arr = new int[5];
delete[] arr;
std::cout << arr[0]; // 未定義動作
ダングリングポインタを防ぐ方法
解放後のポインタをnullptrに設定
int* ptr = new int(10);
delete ptr;
ptr = nullptr; // 安全策
スマートポインタを活用
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(10);
ポインター
ポインターを引数として渡す
void modify(int* p) {
*p = 100; // 呼び出し元の変数を変更
}
int main() {
int a = 10;
modify(&a);
std::cout << "変更後のa: " << a << std::endl; // 100
}
ポインターを返す関数
int* createInt() {
int* p = new int(50);
return p;
}
int main() {
int* num = createInt();
std::cout << *num << std::endl; // 50
delete num; // メモリ解放
}
ポインターと動的メモリ確保の違い
記載の仕方
ポインターは、以下のような記載する。
int* p = &a;
動的メモリ確保は、以下のような記載する。
int* p = new int;
メモリの解放
ポインターは、自動(スコープを抜けると解放)。
動的メモリ確保は、delete / delete[] を使って明示的に解放。この処理を行わないとでないとメモリリークが起きる。スマートポインタ(std::unique_ptrやstd::shared_ptr)だとこのdelete処理を行なくて良い。
動的メモリ確保のメリット
スマートポインター
メリット
スマートポインターとは、deleteが不要なポインターのことである。
これによって手動でnewするよりもメモリリークなどが起きにくくなる。
std::unique_ptrとstd::shared_ptrとstd::weak_ptrについて
スマートポインターには、主に三つある。
std::unique_ptr
std::shared_ptr
std::weak_ptr
である
主な違いは、
所有権を共有である。
なお、NGコードは以下のようなパターンがある。
std::shared_ptr<int> sp2(new int(10));
「コピー不可」とは
「コピー不可」とは、std::unique_ptrで出てくる言葉で以下のようにスマートポインターに他のポインターを代入できないことのことである。
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> p1 = std::make_unique<int>(42);
std::unique_ptr<int> p2 = p1; // ❌ コピーしようとするとコンパイルエラー
}
循環参照とは
循環参照とは、スマートポインター同士がお互いに参照し合うことで、参照カウントが減らず、メモリが解放されなくなる問題のことです。これによりメモリリーク起きます。
特に C++ の std::shared_ptr を使用している場合に発生しやすいです。
なので、std::shared_ptrを使用する際は、std::weak_ptrと併用して循環参照を防ぐことが多い。
参照カウントとは
std::shared_ptrを使用する際に出てくる言葉である。
std::shared_ptrが共有しているポインターの数のことである。
lock
lockとは、主にマルチスレッドプログラミングにおいて、ミューテックス(mutex)をロックするためのメソッドとして使用されます。スレッドが共有リソースにアクセスする際に、競合状態(race condition)を防ぐために使われます。
主に4つが存在する。
std::mutex::lock() / unlock()
std::lock_guard
std::unique_lock
std::lock()
文字と文字列の型
char
サイズ: 通常 1 バイト(8 ビット)
文字セット: ASCII またはマルチバイト文字(UTF-8, Shift_JIS など)
用途: 英数字やシンプルな文字データに適している。日本語などのマルチバイトは使えない。
char c = 'A';
wchar_t
サイズ: 実装によるが、通常 2 バイト(16 ビット)または 4 バイト(32 ビット)
Windows: 2 バイト(UTF-16)
Linux/macOS: 4 バイト(UTF-32)
文字セット: ワイド文字(UTF-16 や UTF-32 など)
用途: Unicode(多言語対応)を扱う場合に適している。日本語などのマルチバイトは使える。
wchar_t wc = L'あ'; // ワイド文字(Lをつける)
ワイド文字
wchar_tとwchar_t*の違い
wchar_tは文字一つ
wchar_t wc = L'A';
wchar_t*は文字列
wchar_t* wstr = (wchar_t*)L"Hello, world!";
wchar_t* に関連する関数は以下などがある。
wcslen(ptr) : ワイド文字列の長さを取得
wcscpy(dest, src) : ワイド文字列のコピー
wcscmp(str1, str2) : ワイド文字列の比較
swprintf(buf, L"%ls", str) : ワイド文字列のフォーマット
char16_t / char32_t(C++11以降)
Unicodeの UTF-16(char16_t)や UTF-32(char32_t)を扱うための型。
char16_t c16 = u'あ';
char32_t c32 = U'あ';
char の配列
char の配列で文字列を表現する方法。
文字列の終端には \0(ヌル文字)が必要。
char str[] = "Hello";
std::cout << str << std::endl; // 出力: Hello
上のHelloは実際は以下である。
文字列の終端には \0(ヌル文字)がつく。
なので、sizeofの実行結果は6になる。
'H' 'e' 'l' 'l' 'o' '\0'
std::string
C++標準ライブラリの std::string クラスを使うと、便利な文字列操作が可能。
- 演算子で結合できる。
std::string s = "Hello";
s += " World!";
std::wstring(ワイド文字列)
std::wstring は wchar_t を使った文字列クラス。
std::wcout を使って出力する。
std::wstring ws = L"こんにちは";
std::wcout << ws << std::endl;
std::stringとstd::wstring(ワイド文字列)の違い
std::u16string / std::u32string(C++11以降)
Unicodeの UTF-16 や UTF-32 を扱うための文字列クラス。
std::u16string u16s = u"こんにちは";
std::u32string u32s = U"こんにちは";
std::string と char 配列の相互変換
std::string → char*
std::string s = "Hello";
const char* cstr = s.c_str();
char* → std::string
const char* cstr = "Hello";
std::string s = cstr;
newとmalloc()の違い
memset
memsetは、効率的に初期化、プログラムの安全性を高めたりするのに役立つ。
ただいくつか注意すべき箇所がある。
例えば、メモリオーバーフロー、ポインタの正しい使用、パフォーマンスの低下など。
_snprintf_s と_wsnprintf_s
_snprintf_sとは
_wsnprintf_sとは
オーバーフロー時に buffer を安全な NULL 終端の空文字列にするとは
バッファサイズの指定方法
typedef structを使用する
typedef struct {
wchar_t* name; // wchar_t型の文字列
int age; // 整数型の年齢
} Person;
int main() {
setlocale(LC_ALL, "jpn");
// Person構造体のインスタンスを作成
Person person;
wchar_t* nameValue = (wchar_t*)L"山田太郎";
// メンバーに値を設定
person.name = nameValue; // ワイド文字列リテラル
person.age = 30;
// 構造体のメンバーを表示
wprintf(L"名前: %ls\n", person.name); // %lsを使ってwchar_t文字列を表示
wprintf(L"年齢: %d\n", person.age);
return 0;
}
実行結果
typedefとtypedef structは複数指定できる
typedef
typedef int Integer, *IntPtr, **IntPtr2;
typedef unsigned long ULong, *ULongPtr;
Integer は int 型
IntPtr は int* 型
IntPtr2 は int** 型
ULong は unsigned long 型
ULongPtr は unsigned long* 型
typedef struct
typedef struct {
int x;
int y;
} Point, *PointPtr;
Point は構造体型 struct に名前を付けたもの
PointPtr は Point 型へのポインタ
Discussion
前回も書いていますが、
という記述だと非常に危険です。これは絶対にやめましょう。