Saya baru-baru ini mengikuti diskusi Reddit yang mengarah ke perbandingan yang bagus dari std::visit
optimasi di kompiler. Saya perhatikan hal berikut: https://godbolt.org/z/D2Q5ED
Baik GCC9 dan Clang9 (saya kira mereka berbagi stdlib yang sama) tidak menghasilkan kode untuk memeriksa dan melempar pengecualian yang tidak bernilai ketika semua jenis memenuhi beberapa persyaratan. Ini mengarah ke codegen cara yang lebih baik, maka saya mengangkat masalah dengan MSVC STL dan disajikan dengan kode ini:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
Klaimnya adalah, bahwa ini membuat varian tidak bernilai, dan membaca dokumen itu harus:
Pertama, hancurkan nilai yang saat ini terkandung (jika ada). Kemudian langsung-menginisialisasi nilai yang terkandung seolah-olah membangun nilai tipe
T_I
dengan argumenstd::forward<Args>(args)....
Jika pengecualian dilemparkan,*this
dapat menjadi valueless_by_exception.
Apa yang saya tidak mengerti: Mengapa itu dinyatakan sebagai "mungkin"? Apakah sah untuk tetap di negara lama jika seluruh operasi dilemparkan? Karena inilah yang dilakukan GCC:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
Dan kemudian (kondisional) melakukan sesuatu seperti:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Oleh karena itu pada dasarnya itu menciptakan sementara, dan jika itu berhasil menyalin / memindahkannya ke tempat nyata.
IMO ini merupakan pelanggaran "Pertama, hancurkan nilai yang saat ini terkandung" sebagaimana dinyatakan oleh dokumen. Ketika saya membaca standar, maka setelah v.emplace(...)
nilai saat ini dalam varian selalu dihancurkan dan tipe baru adalah tipe yang ditetapkan atau tidak berharga.
Saya mendapatkan bahwa kondisi is_trivially_copyable
mengecualikan semua jenis yang memiliki destruktor yang dapat diamati. Jadi ini bisa juga sebagai: "varian as-if diinisialisasi ulang dengan nilai lama" atau lebih. Tetapi keadaan varian adalah efek yang dapat diamati. Jadi apakah standar memang memungkinkan, yang emplace
tidak mengubah nilai saat ini?
Edit sebagai respons terhadap kutipan standar:
Kemudian menginisialisasi nilai yang terkandung seolah-olah langsung-non-daftar-inisialisasi nilai tipe TI dengan argumen
std::forward<Args>(args)...
.
Apakah T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
benar-benar dihitung sebagai implementasi yang valid dari hal di atas? Apakah ini yang dimaksud dengan "seolah-olah"?
might/may
kata - kata karena standar tidak menyatakan apa alternatifnya.there is no way to detect the difference
.Iya.
emplace
harus memberikan jaminan dasar agar tidak bocor (yaitu, menghormati umur objek ketika konstruksi dan penghancuran menghasilkan efek samping yang dapat diamati), tetapi jika memungkinkan, diizinkan untuk memberikan jaminan yang kuat (yaitu, kondisi awal disimpan ketika operasi gagal).variant
diperlukan untuk berperilaku serupa dengan serikat pekerja - alternatif dialokasikan di satu wilayah penyimpanan yang dialokasikan sesuai. Tidak diperbolehkan mengalokasikan memori dinamis. Oleh karena itu, perubahan tipeemplace
tidak memiliki cara untuk menjaga objek asli tanpa memanggil konstruktor bergerak tambahan - ia harus menghancurkannya dan membangun objek baru sebagai pengganti objek tersebut. Jika konstruksi ini gagal, maka varian harus pergi ke keadaan tanpa nilai yang luar biasa. Ini mencegah hal-hal aneh seperti menghancurkan objek yang tidak ada.Namun, untuk jenis kecil yang dapat disalin sepele, dimungkinkan untuk memberikan jaminan yang kuat tanpa terlalu banyak biaya tambahan (bahkan peningkatan kinerja untuk menghindari cek, dalam hal ini). Karena itu, implementasinya melakukannya. Ini sesuai standar: implementasi masih memberikan jaminan dasar seperti yang disyaratkan oleh standar, hanya dengan cara yang lebih ramah pengguna.
Ya, jika pemindahan tugas tidak menghasilkan efek yang dapat diamati, yang merupakan kasus untuk jenis yang dapat disalin secara sepele.
sumber
std::variant
tidak memiliki alasan untuk memecahnya. Saya setuju bahwa ini dapat dibuat lebih eksplisit dalam kata-kata standar, tetapi ini pada dasarnya bagaimana orang lain menjadi bagian dari pekerjaan standar perpustakaan. Dan FYI, P0088 adalah proposal awal.if an exception is thrown during the call toT’s constructor, valid()will be false;
Jadi itu melarang "optimasi" iniemplace
dalam P0088 di bawahException safety