覚書 引用 ChatGPT

 

 

こちらこそ、いつも本質的で良い質問ありがとうございます!
スレッドと static/ローカル変数の関係について、以下に分かりやすくまとめます。
結論(先に要点)
種類    各スレッドで独立?    備考
関数内ローカル変数 独立している    スタック領域に生成、スレッドごとに別物
static 変数(関数内)    ❌ 共通(共有される)    グローバル領域に一つだけ存在する
クラスの static 変数    ❌ 共通(共有される)    全インスタンス&全スレッドで共通
非 static メンバ変数    ✅ インスタンスごとに独立    ただし同じインスタンスを共有したら当然共有される
🧪 詳しく見てみましょう
🧷 1. ローカル変数(関数内で宣言された変数)

static void fcPublic() {
    int localVar = 42; // これは各スレッド独立
}

    これは スレッドごとのスタック領域に確保されます。

    よって、スレッド間で完全に独立します。

    ✔️ 安心して使えます。マルチスレッド安全です。

🧷 2. static ローカル変数(関数の中で static)

static void fcPublic() {
    static int sharedCounter = 0; // これは全スレッドで共通
}

    一度だけ初期化されて共有される。

    複数スレッドから同時アクセスした場合は競合が起きる可能性あり。

    → ミューテックスなどで保護すべきです。

    ⚠️ マルチスレッドでは注意が必要です。

🧷 3. クラスの static メンバ変数

class MyClass {
    static int gCounter; // スレッド共通
};

    全てのスレッド・全てのインスタンスで 共通の一個しか存在しない。

    これもスレッドセーフにするにはロックが必要です。

    ⚠️ 使うなら std::mutex 等で排他制御が必要です。

🧷 4. 非 static メンバ変数

class MyClass {
    int iData; // インスタンスごとに違う
};

    インスタンスをスレッドごとに分ければ問題なし。

    ただし、同じインスタンスを複数スレッドが共有すると競合の可能性あり。

    ✔️/⚠️ 安全かどうかは設計次第です。

🧵 例:std::thread で static メンバ関数を使う

class MyClass {
public:
    static void run() {
        int localVar = 100;         // スレッドごとに独立
        static int staticVar = 0;   // スレッド間で共有、危険
        ++staticVar;
        std::cout << "localVar: " << localVar << ", staticVar: " << staticVar << "\n";
    }
};

std::thread th1(MyClass::run);
std::thread th2(MyClass::run);
th1.join();
th2.join();

    localVar → 問題なし、毎回 100

    staticVar → 競合の可能性。値が 1,2 になる保証はない。データレース。

🧩 対処法:static 変数のスレッドセーフ化
方法1: ミューテックスで保護

static std::mutex mtx;
static int sharedCounter = 0;

std::lock_guard<std::mutex> lock(mtx);
++sharedCounter;

方法2: スレッドローカル変数(thread_local)

static thread_local int threadCounter = 0;

    → スレッドごとに独立な static 変数。

    まさに「スレッドごとの static」が欲しいときに便利。

✅ まとめ(再掲)
変数の種類    スレッド間で共有?    安全に使える?
関数内ローカル変数    ❌ 共有されない    ✅ 安全
static ローカル変数    ✅ 共有される    ⚠️ 要注意
クラスの static メンバ変数    ✅ 共有される    ⚠️ 要注意
非 static メンバ変数    ❌ 共有されない(※)    ✅ or ⚠️(設計次第)
thread_local 変数    ❌ スレッドごと    ✅ 安全

何か試してみたいコードがあれば、書いて一緒に確認もできますよ!
あなた:
ありがとうございます。
質問があります。

    ただし同じインスタンスを共有したら当然共有される

その時、ほぼ同時には代入されないと思いますが、
共有を許容した場合、特別な処理なく、同時に代入しても大丈夫ですか?
ChatGPT:

とても重要な視点です。
結論から言うと:
✅ 結論:同時に代入しては「ダメ」です。

    同じインスタンスのメンバ変数に対して
    複数スレッドが同時に書き込む(代入する)と未定義動作になります。

これは「競合状態(data race)」と呼ばれ、非常に危険です。
🔥 具体例:代入がかち合うとどうなるか

class MyClass {
public:
    int counter = 0;
};

MyClass obj;

std::thread t1([&]() {
    for (int i = 0; i < 1000000; ++i) obj.counter = 1;
});

std::thread t2([&]() {
    for (int i = 0; i < 1000000; ++i) obj.counter = 2;
});

t1.join();
t2.join();

std::cout << "Final value: " << obj.counter << "\n";

    obj.counter に 1 と 2 を交互に高速で書き込む

    処理系・タイミングにより:

        1 や 2 のどちらかに収束するかもしれないし

        破壊された中途半端な値になることすらあります(UB)

    マシンによってはクラッシュや謎挙動も

📛 どうして「代入だけ」でも危険なの?

obj.counter = 1;

    これ1行でも、実は複数ステップに分解されてます:

        アドレス計算

        レジスタへ読み出し

        レジスタに値セット

        メモリ書き戻し

別スレッドも同じことをしてたら?
→ 読み出しと書き戻しが競合して、予測不能な値になります
🛡️ 対処法
1. std::mutex で保護する

std::mutex mtx;

std::thread t1([&]() {
    for (int i = 0; i < 1000000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        obj.counter = 1;
    }
});

→ 同時アクセスしないように排他制御。
2. std::atomic を使う(単純な整数などに限る)

#include <atomic>

class MyClass {
public:
    std::atomic<int> counter = 0;
};

    ロック不要でスレッドセーフにアクセス可能。

    +=, ++, store, load などが使える。

    高速(CPUの命令レベルで排他制御)

✅ まとめ
条件    安全性    対処方法
同一インスタンスに同時読み込み    ✅ 安全    問題なし
同一インスタンスに同時書き込み    ❌ 危険(UB)    mutex または atomic

    💡 「代入だけだし安全でしょ」は C++ では通用しないです。
    処理系依存・CPU依存の落とし穴があります。

覚書 引用 ChatGPT

 

オリジナル(仮)

for (int i = iStart; i < iEnd; ++i) {
    pDst[i] = pSrc[i];
}

高速化版(一行ずつ改変明示)

// 1. restrict でポインタの非エイリアス宣言(ただしC++では非標準。VCなら __restrict 使用)
T* __restrict pS = pSrc + iStart; // 元: pSrc[i]
T* __restrict pD = pDst + iStart; // 元: pDst[i]
int iLen = iEnd - iStart;         // 2. 範囲長を事前計算

int i = 0;
int iLen4 = iLen & ~3;            // 3. 4の倍数部分まで(ループアンローリング用)

// 4. アンローリングループ(4要素ずつコピー)
for (; i < iLen4; i += 4) {
    pD[i    ] = pS[i    ];
    pD[i + 1] = pS[i + 1];
    pD[i + 2] = pS[i + 2];
    pD[i + 3] = pS[i + 3];
}

// 5. 余り(最大3要素)をコピー
for (; i < iLen; ++i) {
    pD[i] = pS[i];
}

コメント付きまとめ
行    内容    説明
1-2    __restrict 使用    ポインタが非エイリアスであると仮定することで最適化しやすくなる(MSVCで効果大)
3    iLen    範囲長を事前に計算し、ループの条件式から差分を排除
4-9    4連アンローリング    実行パイプライン効率化、分岐回数減少
11-13    余り要素処理    長さが4で割り切れない場合の残り処理

覚書。

        // 呼び出し側が高速に使える forEach 的な手法
        //これは「関数の中に関数を渡して処理する」=高階関数というテクです!
        // fnとは?渡されたラムダ関数・関数ポインタ・関数オブジェクトのこと。
        //// 全要素を加算する処理(呼び出し側は何も意識せず!)
        template <typename Func>
        void forEach(Func&& fn) {
            T* pVec = this->mVector;
            for (size_t i = 0; i < piSize; ++i) {
#ifdef PREFETCH_OPT
                __builtin_prefetch(&pVec[i + 1], 0, 1);
#endif
                fn(pVec[i]); // ← fnがここで1要素に対する処理を行っている!
            }
        }
        //        //呼び出し方
        //        void foo(){
        //            int sum = 0;
        //            arr.forEach([&](int& val) {
        //                sum += val;  // ← fnの中身
        //            });
        //        }

 

引用 ChatGPT

 

私はフォルダ名・ファイル名には必ず日付の6桁(YYMMDD)を付けている。ファイラーに時系列でソートされているため使いやすい。

このソフトはクリップボードにYYMMDDの6桁の数字を入れてくれる。タスクバーにピン留していて、後はぺするだけ。

改行を削除したのは、読んでもらうため。コードは素人丸出しです。

 

#include <iostream>
#include <filesystem>
#include <thread>
#include <chrono>
#include <Windows.h>
#include "shlobj_core.h"
inline BOOL SetClipboardText(const char* str) {int BufSize;char* Buf;HANDLE hMem;BufSize = strlen(str) + 1;hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, BufSize);if (!hMem) {return false;}Buf = (char*)GlobalLock(hMem);if (Buf) {strcpy_s(Buf, 32, str);GlobalUnlock(hMem);if (OpenClipboard(NULL)) {EmptyClipboard();SetClipboardData(CF_TEXT, hMem);CloseClipboard();return true;}}return false;}inline std::string fcStrYYMMDD() {auto now = std::chrono::system_clock::now();auto t = std::chrono::system_clock::to_time_t(now); auto tm = *std::localtime(&t); std::ostringstream oss; oss << std::put_time(&tm, "%Y%m%d"); return oss.str().erase(0, 2); }inline void fcYYMMDD_Clipboard() { std::string str = fcStrYYMMDD(); const char* datetime = str.c_str(); if (!SetClipboardText(datetime)) { std::cout << "error!\n"; getchar(); } }inline void fcHolder_ClipboardCopy(std::string& strFN) {const char* path = strFN.c_str(); HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(DROPFILES) + strlen(path) + 2); if (!hGlobal) { return; }DROPFILES* dropfiles = (DROPFILES*)GlobalLock(hGlobal); if (!dropfiles) { GlobalFree(hGlobal); return; }dropfiles->pFiles = sizeof(DROPFILES); dropfiles->fNC = TRUE; dropfiles->fWide = FALSE; memcpy(&dropfiles[1], path, strlen(path)); GlobalUnlock(hGlobal); if (!OpenClipboard(NULL)) {GlobalFree(hGlobal);return;}if (!EmptyClipboard()) {GlobalFree(hGlobal);return;}if (!SetClipboardData(CF_HDROP, hGlobal)) {GlobalFree(hGlobal);return;}GlobalFree(hGlobal);CloseClipboard();return;}inline void fc_N_Minits(int& iN) {for (int i = iN; i >= 1; i--) {std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << i << "秒";if (i != 1) { std::cout << "..."; }}std::cout << "\n\n";}inline void fcYYMMDD_Holder_Clipboard() {std::string strYYMMDD = fcStrYYMMDD();std::string strCrentPath = std::filesystem::current_path().string<char>();std::string strFN = strCrentPath + "\\" + strYYMMDD;std::filesystem::create_directory(strFN);fcHolder_ClipboardCopy(strFN);int iN = 10;std::cout <<"■■■ IronYYMMDD2 ■■■\n\n""YYMMDDフォルダサービスを開始しました。\n"<<iN<<"秒後にサービスを終了します。\n";fc_N_Minits(iN);std::filesystem::remove_all(strFN);iN = 5;std::cout <<"YYMMDDフォルダサービスを終了しました。\n""クリップボードからフォルダ貼り付けはできません。\n代わりにクリップボードにYYMMDDを挿入しました。"<< iN<< "秒後、自動的にプログラムを終了します。\n";fc_N_Minits(iN);fcYYMMDD_Clipboard();return;}int main() {if (GetAsyncKeyState(VK_SHIFT) != 0) {fcYYMMDD_Holder_Clipboard();}fcYYMMDD_Clipboard(); return 0;}

ある日、if文の中かっこ{}の省略できることを知った。

if文の処理内容が1行なら{}がなくてもコンパイルが通る。

なんかかっこよかったので、かなり乱用した。
・・・

起こりました。

 

・・・

その日から必ず{}を入れるようになりました。

 

メリット

その日、気分がいい。

 

デメリット

いつか、気分を悪くする。

クラス継承によって関数呼び出しが、遅くなるとの報告。数値解析等の時間がおしい計算でのパフォーマンスの影響は?気になる。

 

引用

https://puarts.com/?pid=988

 

 

 

引用元は14年前の記事なので、現在2025年の計測をして見たい。

ここに書く

覚書

あなたの仮説通り、スレッド作成コストと同期オーバーヘッドが重いです。
swap専用スレッド + 通知待機型の雛形(std::condition_variable)

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void swap_worker() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return ready; }); // 条件待ち

        // ここでswap処理
        ready = false;
    }
}

これならCPU 100%にならず、省エネで待機できます。

 

引用 ChatGPT

覚書。

前から謎だった件。

std::threadであまり速度が速くならなかったので、オーバーヘッドだなと。

だとしたら、関数専用スレッドを作ったらから始まった。

 

while(true) ループでCPUが100%にならない?

結論:
    何も入っていないループはCPUを100%使います。
    しかし**「軽すぎて人間が気づかない」**のと、
    OSスケジューラが「ほぼ無駄な仕事」と判断して他スレッドに回すため、 あまり深刻に見えないことも多いです。

もし以下のように「本当に何もしていないループ」があれば:

while (!flag) {
    // 本当に何もしてない
}

これが何十万回/秒で回ってたら、当然CPUは忙殺されます。

対策:
#include <thread>
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 負荷を逃がす

あるいは std::condition_variable を使えば OS がちゃんと「寝かせてくれます」(これが次の 5 の話)。

5. condition_variable::wait の省エネ仕組みとは?

std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });

このとき、スレッドは OS に「休ませてください」と伝えることになります。 → OSはスレッドを完全にスリープ状態に入れる
→ CPUリソースを一切使いません
つまり:

    ポーリングループ(busy wait):CPU酷使
    条件付きwait:CPUリソース0、めっちゃ省エネ

GUIアプリが多用するのはこの省エネ型です。
GUIの謎ループの中身も実はこう:

while (GetMessage(&msg, nullptr, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

→ GetMessage 内部が OSのwaitシステムコール で「新しいイベントが来るまで寝る」
→ CPUはアイドル状態。省エネです。

 まとめ
項目    ポイント
while(true) ループ    何もしないと100%CPU食う。軽い処理&OSの最適化で目立たないだけ
cv.wait()    スレッドを完全にスリープさせて省エネ

引用 ChatGPT

覚書。

this-> の意味    テンプレート継承で名前解決のため。速度的にはローカル化推奨
 

3. this-> はなぜ使うの? 継承との関係は?
継承先で使っている this->member は、テンプレート + 継承の組み合わせで必要になる場面があります。

template<typename T>
class Base {
protected:
    T mVector[10];
};

template<typename T>
class Derived : public Base<T> {
public:
    T& operator()(size_t i) {
        return this->mVector[i]; // this-> が必要
    }
};

これは「Base<T>::mVector がテンプレートによって名前解決が遅延されている」ため、this-> で明示しないとコンパイルできないことがあるんです。
速度についての補足:
    this->mVector[i] は実際には *(this + offset) 的なアクセスになる。
    対して、T* p = this->mVector; → p[i] にすると、レジスタに乗る確率がグッと上がる。
つまり:
auto& vec = this->mVector;
for (...) {
    do_something(vec[i]); // 高速になることが多い
}

 

auto& vec = this->mVector; はディープコピーか?
いい着眼点です!
 答え:NO、 これはディープコピー ではない!
auto& vec = this->mVector;
これは「参照(reference)を束ねてるだけ」です。
つまり vec[i] はそのまま this->mVector[i] と同じメモリを見ていることになります。
したがって:
inline T& operator()(const size_t& c) noexcept {
    return this->mVector[c];  // <- これは妥当
}
これ自体も 1回のアクセスごとに this-> のオフセット参照が発生するだけで、それほど重くないです。 でも、頻繁に呼ばれる場合は、この mVector を関数内で参照にキャッシュしたほうが速くなるケースもあります。
 結論(速度重視まとめ)
状況    おすすめ書き方    備考
単発呼び出し    this->mVector[c]    問題なし
高頻度アクセス    auto& vec = this->mVector; → vec[c]    レジスタ化・オフセット計算の省略効果あり
operator() 内部で使う    無理に最適化しなくてOK    ただし中で複数回アクセスするなら auto& 化あり

 

この変換はキャッシュヒット率&レジスタ最適化の観点で効果があります。

 

引用 ChatGPT

覚書。

 

1. memsetはint以外でも使える?
ゼロ初期化目的であればOKな型:
    int, float, double, bool, char, short, long, unsigned 系
    いわゆる「POD(Plain Old Data)」型ならOK ❌ NGや危険:
    ポインタ(アーキテクチャ依存でNULLポインタが非0バイトになる場合あり)
    オブジェクト型(コンストラクタ・デストラクタを持つ)
memsetでゼロ埋めしたいなら、「すべてのビットが0で意味がある」型だけにするのが安全。

 

2. std::is_same で memset分岐する方法?
if constexpr (std::is_same<T, int>::value || std::is_same<T, double>::value || ...) {
    std::memset(...);  // OK
} else {
    // 何もしない or static_assert でコンパイルエラー
}

 

テンプレート制約ならこう:
static_assert(
    std::is_same<T, int>::value || std::is_same<T, float>::value || ...,
    "T must be a POD type suitable for memset");

3. コンパイルエラーを強制する方法
static_assert(std::is_same<T, int>::value || std::is_same<T, double>::value,
              "This array class supports only primitive types");
これでオブジェクト型や構造体を使うと即ビルドエラーにできます。

 

4. memsetゼロ初期化OKなプリミティブ型一覧
    int, unsigned int
    short, unsigned short
    long, unsigned long
    long long, unsigned long long
    float, double, long double
    bool
    char, unsigned char, wchar_t, char16_t, char32_t
    std::byte(C++17~)  共通点:全ビットが 0 のとき「意味を持つ」型

 

引用 ChatGPT