Apakah kompiler saya mengabaikan anggota thread_local statis saya yang tidak digunakan?

10

Saya ingin melakukan pendaftaran utas di kelas saya, jadi saya memutuskan untuk menambahkan tanda centang untuk thread_localfitur ini:

#include <iostream>
#include <thread>

class Foo {
 public:
  Foo() {
    std::cout << "Foo()" << std::endl;
  }
  ~Foo() {
    std::cout << "~Foo()" << std::endl;
  }
};

class Bar {
 public:
  Bar() {
    std::cout << "Bar()" << std::endl;
    //foo;
  }
  ~Bar() {
    std::cout << "~Bar()" << std::endl;
  }
 private:
  static thread_local Foo foo;
};

thread_local Foo Bar::foo;

void worker() {
  {
    std::cout << "enter block" << std::endl;
    Bar bar1;
    Bar bar2;
    std::cout << "exit block" << std::endl;
  }
}

int main() {
  std::thread t1(worker);
  std::thread t2(worker);
  t1.join();
  t2.join();
  std::cout << "thread died" << std::endl;
}

Kode itu sederhana. Saya Barkelas memiliki statis thread_localanggota foo. Jika statis thread_local Foo foodibuat, itu berarti utas dibuat.

Tetapi ketika saya menjalankan kode, tidak ada dalam Foo()cetakan, dan jika saya menghapus komentar di Barkonstruktor, yang menggunakan foo, kode berfungsi dengan baik.

Saya mencoba ini pada GCC (7.4.0) dan Dentang (6.0.0) dan hasilnya sama. Saya kira kompiler menemukan bahwa footidak digunakan dan tidak menghasilkan instance. Begitu

  1. Apakah kompiler mengabaikan static thread_localanggota? Bagaimana saya bisa men-debug untuk ini?
  2. Jika demikian, mengapa anggota normal statictidak memiliki masalah ini?
ravenisadesk
sumber

Jawaban:

9

Tidak ada masalah dengan pengamatan Anda. [basic.stc.static] / 2 melarang penghapusan variabel dengan durasi penyimpanan statis:

Jika variabel dengan durasi penyimpanan statis memiliki inisialisasi atau destruktor dengan efek samping, itu tidak akan dihilangkan bahkan jika tampaknya tidak digunakan, kecuali bahwa objek kelas atau salinan / gerakannya dapat dihilangkan seperti yang ditentukan dalam [class.copy] .

Pembatasan ini tidak berlaku untuk durasi penyimpanan lain. Faktanya, [basic.stc.thread] / 2 mengatakan:

Variabel dengan durasi penyimpanan ulir harus diinisialisasi sebelum digunakan pertama kali dan, jika dibangun , harus dihancurkan saat keluar ulir.

Ini menunjukkan bahwa variabel dengan durasi penyimpanan thread tidak perlu dibangun kecuali digunakan odr.


Tetapi mengapa perbedaan ini?

Untuk durasi penyimpanan statis, hanya ada satu instance variabel per program. Efek samping dari konstruksi dapat menjadi signifikan (agak seperti konstruktor seluruh program), sehingga efek samping diperlukan.

Namun, untuk durasi penyimpanan lokal utas, ada masalah: suatu algoritma dapat memulai banyak utas. Untuk sebagian besar utas ini, variabelnya sama sekali tidak relevan. Akan lucu jika perpustakaan simulasi fisika eksternal yang memanggil std::reduce(std::execution::par_unseq, first, last)akhirnya membuat banyakfoo contoh, kan?

Tentu saja, bisa ada penggunaan yang sah untuk efek samping dari pembangunan variabel durasi penyimpanan lokal thread yang tidak digunakan odr (misalnya, pelacak thread). Namun, keuntungan untuk menjamin ini tidak cukup untuk mengimbangi kelemahan yang disebutkan di atas, sehingga variabel-variabel ini diizinkan untuk dihilangkan selama mereka tidak digunakan. (Kompiler Anda dapat memilih untuk tidak melakukannya. Dan Anda juga dapat membuat pembungkus Anda sendiri std::threadyang menangani hal ini.)

LF
sumber
1
Oke ... jika standar mengatakannya, maka jadilah itu ... Tapi bukankah aneh kalau panitia tidak menganggap efek samping sebagai durasi penyimpanan statis?
ravenisadesk
@reavenisadesk Lihat jawaban yang diperbarui.
LF
1

Saya menemukan informasi ini di " Penanganan ELF Untuk Penyimpanan Thread-Lokal " yang dapat membuktikan jawaban @LF

Selain itu dukungan run-time harus menghindari pembuatan penyimpanan thread-lokal jika tidak diperlukan. Sebagai contoh, modul yang dimuat hanya dapat digunakan oleh satu thread dari banyak yang membentuk proses. Akan membuang-buang memori dan waktu untuk mengalokasikan penyimpanan untuk semua utas. Metode malas dibutuhkan. Ini tidak banyak beban tambahan karena persyaratan untuk menangani objek yang dimuat secara dinamis sudah membutuhkan penyimpanan yang mengenali yang belum dialokasikan. Ini adalah satu-satunya alternatif untuk menghentikan semua utas dan mengalokasikan penyimpanan untuk semua utas sebelum membiarkannya berjalan kembali.

ravenisadesk
sumber