Thread Local Storage解放時の振る舞い

追記1

クラススコープのnew/delete勘違いしてたので修正

追記2

スタックトレース見てたらlibcから呼ばれていたので,もしかしてとstdlibc++からlibc++に変更したところdeleteが呼ばれなくなりました. つまりはclang/gcc依存ではなくC標準ライブラリの実装依存のようです.

本題

clang/gcc stdlibc++ ではthread_localで宣言された変数はdelete経由で破棄されます. new/deleteをoverloadして自前のMemoryManagerを作っている場合に困るのは諦めるしかなさそうという話です.

対策としてはthread_localポインターで宣言してスレッド終了前にreleaseなり用意して自分でdeleteするしか無いようです.

クラススコープのnew/deleteはnewが反応しないからかdeleteに入ってきませんでした. クラススコープのnew/deleteはそういうものではない.

蛇足

規格書(6.7.2 Thread storage duration)も確認したのですが

A variable with thread storage duration shall be initialized before its first odr-use (6.2) and, if constructed, shall be destroyed on thread exit.

破棄されるとしか書いてないのでどう破棄するかは実装依存みたいです.VS2017ではdelete経由ではありませんでした. デストラクタ呼ぶだけの実装になってくれないのかなあと思うのですがC++はよくわからないのと,自分の検索能力では情報を見つけられなかったので諦めて迂回手段を執ることにします.

以下再現コード(wandboxだと内部的に利用されているインスタンスのdeleteも拾ってしまうので適当な環境で実行してください)

#include <iostream>
#include <cstdlib>

template <typename tDerived>
class TSingletonTLS
{
  public:
    static tDerived &get()
    {
        static thread_local tDerived instance;
        use(instance);
        return instance;
    }

    ~TSingletonTLS(){};
  protected:
    TSingletonTLS(){};

  private:
    static void use(tDerived const &) {}
};

class test_tls : public TSingletonTLS<test_tls>
{
public:
    void print(){ std::cout << "call test_tls.print()" << std::endl; }
};

void operator delete(void*) noexcept
{
    std::cout << "call overloaded delete" << std::endl;
}
void operator delete(void*, std::size_t) noexcept
{
    std::cout << "call overloaded delete" << std::endl;
}

int main()
{
    test_tls::get().print();
}