Halo, Habr! Kami mempersembahkan kepada Anda terjemahan dari artikel "Semua yang Harus Anda Ketahui Tentang std :: any from C ++ 17" oleh Bartlomiej Filipek .
Dengan bantuan std::optional
Anda dapat menyimpan satu jenis jenis. Dengan bantuan std::variant
Anda dapat menyimpan beberapa jenis dalam satu objek. Dan C ++ 17 memberi kami jenis pembungkus lain - std::any
yang dapat menyimpan apa pun sementara jenis lainnya aman.Dasar
Sebelum ini, standar C ++ tidak memberikan banyak solusi untuk masalah penyimpanan beberapa tipe dalam satu variabel. Tentu saja Anda dapat menggunakannya void*
, tetapi sama sekali tidak aman.Secara teoritis, void*
Anda bisa membungkusnya dalam kelas di mana Anda dapat menyimpan jenisnya:class MyAny
{
void* _value;
TypeInfo _typeInfo;
};
Seperti yang Anda lihat, kami mendapatkan formulir dasar tertentu std::any
, tetapi untuk memastikan keamanan tipe MyAny
kami memerlukan pemeriksaan tambahan. Itulah mengapa lebih baik menggunakan opsi dari perpustakaan standar daripada membuat keputusan sendiri.Dan ini adalah apa itu std::any
dari C ++ 17. Ini memungkinkan Anda untuk menyimpan apa pun di objek dan melaporkan kesalahan (melempar pengecualian) ketika Anda mencoba mengakses dengan menentukan jenis yang salah.Demo kecil:std::any a(12);
a = std::string("Hello!");
a = 16;
std::cout << std::any_cast<int>(a) << '\n';
try
{
std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
a.reset();
if (!a.has_value())
{
std::cout << "a is empty!" << "\n";
}
std::map<std::string, std::any> m;
m["integer"] = 10;
m["string"] = std::string("Hello World");
m["float"] = 1.0f;
for (auto &[key, val] : m)
{
if (val.type() == typeid(int))
std::cout << "int: " << std::any_cast<int>(val) << "\n";
else if (val.type() == typeid(std::string))
std::cout << "string: " << std::any_cast<std::string>(val) << "\n";
else if (val.type() == typeid(float))
std::cout << "float: " << std::any_cast<float>(val) << "\n";
}
Kode ini akan menampilkan:16
bad any_cast
a is empty!
float: 1
int: 10
string: Hello World
Contoh di atas menunjukkan beberapa hal penting:std::any
โ std::optional
std::variant
- ,
.has_value()
.reset()
std::decay
- ,
std::any_cast
, bad_any_cast
, ยซTยป.type()
, std::type_info
Contoh di atas terlihat mengesankan - variabel tipe nyata dalam C ++! Jika Anda benar-benar menyukai JavaScript, Anda bahkan dapat membuat semua variabel tipe Anda std::any
dan menggunakan C ++ sebagai JavaScript :)Tapi mungkin ada beberapa contoh penggunaan normal?Kapan menggunakannya?
Meskipun saya void*
anggap sebagai hal yang sangat tidak aman dengan rentang penggunaan yang sangat terbatas, ini std::any
sepenuhnya aman untuk jenis, sehingga memiliki beberapa cara yang baik untuk menggunakannya.Contohnya:- Di perpustakaan - saat perpustakaan Anda perlu menyimpan atau mentransfer beberapa data, dan Anda tidak tahu tipe data ini
- Saat mem-parsing file - jika Anda benar-benar tidak dapat menentukan jenis apa yang didukung
- Olahpesan
- Interaksi Bahasa Scripting
- Membuat penerjemah untuk bahasa skrip
- Antarmuka Pengguna - Bidang Dapat Menyimpan Apa Pun
Tampak bagi saya bahwa dalam banyak contoh ini kita dapat menyoroti daftar terbatas dari jenis yang didukung, jadi ini std::variant
mungkin pilihan yang lebih baik. Tetapi tentu saja sulit untuk membuat perpustakaan tanpa mengetahui produk akhir yang akan digunakan. Anda tidak tahu jenis apa yang akan disimpan di sana.Peragaan menunjukkan beberapa hal dasar, tetapi di bagian berikut Anda akan belajar lebih banyak std::any
, jadi teruslah membaca.Buat std :: any
Ada beberapa cara untuk membuat objek bertipe std::any
:- inisialisasi standar - objek kosong
- inisialisasi langsung dengan nilai / objek
- langsung menunjukkan jenis objek -
std::in_place_type
- melalui
std::make_any
Contohnya:
std::any a;
assert(!a.has_value());
std::any a2(10);
std::any a3(MyType(10, 11));
std::any a4(std::in_place_type<MyType>, 10, 11);
std::any a5{std::in_place_type<std::string>, "Hello World"};
std::any a6 = std::make_any<std::string>("Hello World");
Ubah nilai
Ada std::any
dua cara untuk mengubah nilai yang saat ini disimpan di : metode emplace
atau tugas:std::any a;
a = MyType(10, 11);
a = std::string("Hello");
a.emplace<float>(100.5f);
a.emplace<std::vector<int>>({10, 11, 12, 13});
a.emplace<MyType>(10, 11);
Siklus Hidup Obyek
Kunci dari keamanan std::any
adalah kurangnya kebocoran sumber daya. Untuk mencapai ini, itu akan std::any
menghancurkan objek aktif apa pun sebelum menetapkan nilai baru.std::any var = std::make_any<MyType>();
var = 100.0f;
std::cout << std::any_cast<float>(var) << "\n";
Kode ini akan menampilkan yang berikut:MyType::MyType
MyType::~MyType
100
Objek std::any
diinisialisasi dengan objek tipe MyType, tetapi sebelum menetapkan nilai baru (100.0f), destruktor disebut MyType
.Mendapatkan akses ke suatu nilai
Dalam kebanyakan kasus, Anda hanya memiliki satu cara untuk mengakses nilai di std::any
- std::any_cast
, ini mengembalikan nilai dari tipe yang ditentukan jika disimpan dalam objek.Fitur ini sangat berguna, karena memiliki banyak cara untuk menggunakannya:- mengembalikan salinan nilai dan berhenti
std::bad_any_cast
karena kesalahan - mengembalikan tautan ke nilai dan berhenti
std::bad_any_cast
karena kesalahan - mengembalikan pointer ke nilai (konstan atau tidak) atau nullptr jika terjadi kesalahan
Lihat sebuah contoh:struct MyType
{
int a, b;
MyType(int x, int y) : a(x), b(y) { }
void Print() { std::cout << a << ", " << b << "\n"; }
};
int main()
{
std::any var = std::make_any<MyType>(10, 10);
try
{
std::any_cast<MyType&>(var).Print();
std::any_cast<MyType&>(var).a = 11;
std::any_cast<MyType&>(var).Print();
std::any_cast<int>(var);
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
int* p = std::any_cast<int>(&var);
std::cout << (p ? "contains int... \n" : "doesn't contain an int...\n");
MyType* pt = std::any_cast<MyType>(&var);
if (pt)
{
pt->a = 12;
std::any_cast<MyType&>(var).Print();
}
}
Seperti yang Anda lihat, kami memiliki dua cara untuk melacak kesalahan: melalui pengecualian ( std::bad_any_cast
) atau mengembalikan pointer (atau nullptr
). Fungsi std::any_cast
untuk pointer kembali kelebihan beban dan ditandai sebagai noexcept
.Performa dan penggunaan memori
std::any
Ini terlihat seperti alat yang ampuh, dan Anda kemungkinan besar akan menggunakannya untuk menyimpan data dari berbagai jenis, tetapi berapa harganya?Masalah utama adalah alokasi memori tambahan.std::variant
dan std::optional
tidak memerlukan alokasi memori tambahan, tetapi ini karena jenis data yang disimpan dalam objek diketahui sebelumnya. std :: any tidak memiliki informasi seperti itu, sehingga dapat menggunakan memori tambahan.Apakah ini akan selalu terjadi atau kadang-kadang? Aturan mana? Apakah ini akan terjadi dengan tipe sederhana seperti int?Mari kita lihat apa yang dikatakan standar:Implementasi harus menghindari penggunaan memori yang dialokasikan secara dinamis untuk nilai yang terkandung kecil. Contoh: di mana objek yang dibangun hanya memegang int. Optimalisasi objek kecil seperti itu hanya akan diterapkan pada tipe T yang is_nothrow_move_constructible_v benar
Implementasi harus menghindari penggunaan memori dinamis untuk data tersimpan berukuran kecil. Misalnya, ketika sebuah objek dibuat hanya menyimpan int. Optimalisasi seperti itu untuk objek kecil seharusnya hanya diterapkan pada tipe T yang is_nothrow_move_constructible_v benar.
Akibatnya, mereka mengusulkan untuk menggunakan Small Buffer Optimization / SBO untuk implementasi. Tapi ini juga ada harganya. Ini membuat tipe lebih besar - untuk menutupi buffer.Mari kita lihat ukurannya std::any
, berikut ini adalah hasil dari beberapa kompiler:Secara umum, seperti yang Anda lihat, std::any
ini bukan tipe yang sederhana, dan itu membawa biaya tambahan. Biasanya memakan banyak memori, karena SBO, dari 16 hingga 32 byte (dalam GCC atau dentang ... atau bahkan 64 byte dalam MSVC!).Bermigrasi dari boost :: any
boost::any
Itu diperkenalkan di suatu tempat pada tahun 2001 (versi 1.23.0). Selain itu, penulis boost::any
(Kevlin Henney) juga merupakan penulis proposal std::any
. Oleh karena itu, kedua jenis ini terkait erat, versi dari STL sangat didasarkan pada pendahulunya.Berikut adalah perubahan utamanya:Perbedaan utama adalah bahwa ia boost::any
tidak menggunakan SBO, sehingga membutuhkan memori yang jauh lebih sedikit (dalam GCC8.1 ukurannya adalah 8 byte), tetapi karena ini, ia secara dinamis mengalokasikan memori bahkan untuk tipe kecil seperti int.Contoh penggunaan std :: any
Kelebihan utama std::any
adalah fleksibilitas. Dalam contoh di bawah ini, Anda dapat melihat beberapa ide (atau implementasi spesifik) di mana menggunakannya std::any
membuat aplikasi sedikit lebih mudah.Penguraian file
Dalam contoh-contoh untuk std::variant
( Anda dapat melihatnya di sini [eng] ), Anda bisa melihat bagaimana Anda dapat mengurai file konfigurasi dan menyimpan hasilnya dalam tipe variabel std::variant
. Sekarang Anda menulis solusi yang sangat umum, mungkin itu adalah bagian dari beberapa perpustakaan, maka Anda mungkin tidak mengetahui semua jenis tipe yang mungkin.Menyimpan data menggunakan std::any
parameter cenderung cukup baik dalam hal kinerja, dan pada saat yang sama memberi Anda fleksibilitas dari solusi.Olahpesan
Di Windows Api, yang terutama ditulis dalam C, ada sistem pesan yang menggunakan id pesan dengan dua parameter opsional yang menyimpan data pesan. Berdasarkan mekanisme ini, Anda dapat mengimplementasikan WndProc, yang memproses pesan yang dikirim ke jendela Anda.LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
Faktanya adalah bahwa data disimpan dalam wParam
atau lParam
dalam berbagai bentuk. Terkadang Anda hanya perlu menggunakan beberapa byte wParam
.Bagaimana jika kita mengubah sistem ini sehingga pesan dapat meneruskan apa pun ke metode pemrosesan?Contohnya:class Message
{
public:
enum class Type
{
Init,
Closing,
ShowWindow,
DrawWindow
};
public:
explicit Message(Type type, std::any param) :
mType(type),
mParam(param)
{ }
explicit Message(Type type) :
mType(type)
{ }
Type mType;
std::any mParam;
};
class Window
{
public:
virtual void HandleMessage(const Message& msg) = 0;
};
Misalnya, Anda dapat mengirim pesan ke jendela:Message m(Message::Type::ShowWindow, std::make_pair(10, 11));
yourWindow.HandleMessage(m);
Sebuah jendela dapat membalas pesan seperti ini:switch (msg.mType) {
case Message::Type::ShowWindow:
{
auto pos = std::any_cast<std::pair<int, int>>(msg.mParam);
std::cout << "ShowWidow: "
<< pos.first << ", "
<< pos.second << "\n";
break;
}
}
Tentu saja, Anda harus menentukan bagaimana tipe data disimpan dalam pesan, tetapi sekarang Anda dapat menggunakan tipe nyata daripada berbagai trik dengan angka.Properti
Dokumen asli yang diwakili oleh apa pun untuk C ++ (N1939) menunjukkan contoh objek properti:struct property
{
property();
property(const std::string &, const std::any &);
std::string name;
std::any value;
};
typedef std::vector<property> properties;
Objek ini terlihat sangat berguna karena dapat menyimpan berbagai jenis. Yang pertama muncul di benak saya adalah contoh menggunakannya di antarmuka pengguna atau editor game.Kami melewati perbatasan
Di r / cpp ada aliran tentang std :: any. Dan setidaknya ada satu komentar hebat yang merangkum kapan suatu tipe harus digunakan.Dari komentar ini :Intinya adalah std :: any memungkinkan Anda untuk mentransfer hak atas data sewenang-wenang lintas batas yang tidak tahu jenisnya.
Semua yang saya bicarakan sebelumnya dekat dengan ide ini:- di perpustakaan untuk antarmuka: Anda tidak tahu jenis apa yang ingin digunakan klien di sana
- olahpesan: ide yang sama - memberikan fleksibilitas pelanggan
- parsing file: untuk mendukung semua jenis
Total
Dalam artikel ini, kami belajar banyak tentang std::any
!Berikut adalah beberapa hal yang perlu diingat:std::any
bukan kelas templatstd::any
menggunakan pengoptimalan objek kecil, sehingga tidak akan secara dinamis mengalokasikan memori untuk tipe sederhana seperti int atau double, dan untuk tipe yang lebih besar, memori tambahan akan digunakanstd::any
bisa disebut "berat", tetapi menawarkan keamanan dan fleksibilitas yang lebih besar- Akses ke data
std::any
dapat diperoleh dengan bantuan any_cast
, yang menawarkan beberapa "mode". Misalnya, jika terjadi kesalahan, ini dapat menimbulkan pengecualian atau hanya mengembalikan nullptr - menggunakannya ketika Anda tidak tahu persis tipe data apa yang mungkin, jika tidak pertimbangkan untuk menggunakannya
std::variant