Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Seperti yang dijelaskan dalam API Penulis menggunakan C++/WinRT, ketika Anda membuat objek dari jenis implementasi, Anda harus menggunakan sekumpulan pembantu winrt::make untuk melakukannya. Topik ini mendalami fitur C++/WinRT 2.0, yang membantu Anda mendiagnosis kesalahan mengalokasikan objek tipe implementasi secara langsung di stack.
Kesalahan tersebut dapat berubah menjadi kegagalan atau kerusakan misterius yang sulit dan memakan waktu untuk diperbaiki. Jadi ini adalah fitur penting, dan ada baiknya memahami latar belakang.
Menyiapkan latar, dengan MyStringable
Pertama, kita pertimbangkan implementasi yang lebih sederhana dari IStringable.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const { return L"MyStringable"; }
};
Sekarang bayangkan bahwa Anda perlu memanggil fungsi (dari dalam implementasi Anda) yang membutuhkan IStringable sebagai argumen.
void Print(IStringable const& stringable)
{
printf("%ls\n", stringable.ToString().c_str());
}
Masalahnya adalah bahwa jenis MyStringable kami tidakmerupakanIStringable.
- Jenis
MyStringable kami adalah implementasi dari antarmuka IStringable. - Tipe IStringable adalah tipe yang diproyeksikan.
Penting
Penting untuk memahami perbedaan antara jenis implementasi dan jenis yang diproyeksikan. Untuk konsep dan istilah penting, pastikan untuk membaca Menggunakan API dengan C++/WinRT dan API Penulis dengan C++/WinRT.
Ruang di antara implementasi dan proyeksi bisa saja sulit untuk dipahami. Dan pada kenyataannya, untuk mencoba membuat implementasi terasa sedikit lebih seperti proyeksi, implementasi memberikan konversi implisit ke masing-masing jenis yang diproyeksikan yang diterapkannya. Itu tidak berarti kita bisa melakukan ini.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const;
void Call()
{
Print(this);
}
};
Sebagai gantinya, kita perlu mendapatkan referensi sehingga operator konversi dapat digunakan sebagai kandidat untuk menyelesaikan panggilan.
void Call()
{
Print(*this);
}
Itu berhasil. Konversi implisit memberikan konversi (sangat efisien) dari jenis implementasi ke jenis yang diproyeksikan, dan itu sangat nyaman untuk banyak skenario. Tanpa fasilitas itu, banyak jenis implementasi akan terbukti sangat rumit bagi penulis. Asalkan Anda hanya menggunakan templat fungsi winrt::make (atau winrt::make_self) untuk mengalokasikan implementasi, maka semuanya baik-baik saja.
IStringable stringable{ winrt::make<MyStringable>() };
Jebakan potensial dengan C++/WinRT 1.0
Namun, konversi implisit dapat membuat Anda dalam kesulitan. Pertimbangkan fungsi pembantu yang tidak membantu ini.
IStringable MakeStringable()
{
return MyStringable(); // Incorrect.
}
Atau bahkan pernyataan ini rupanya tidak berbahaya.
IStringable stringable{ MyStringable() }; // Also incorrect.
Sayangnya, kode seperti itu dikompilasi dengan C++/WinRT 1.0, karena konversi implisit itu. Masalah (sangat serius) adalah bahwa kami berpotensi mengembalikan tipe yang diproyeksikan yang menunjuk ke objek yang dihitung referensinya, di mana memori pendukungnya terletak di tumpukan efemeral.
Berikut adalah sesuatu yang lain yang dikompilasi dengan C++/WinRT 1.0.
MyStringable* stringable{ new MyStringable() }; // Very inadvisable.
Pointer mentah adalah sumber bug yang berbahaya dan menyita waktu. Jangan gunakan jika Anda tidak perlu. C++/WinRT berusaha keras untuk membuat semua hal menjadi efisien tanpa pernah memaksa Anda menggunakan pointer mentah. Berikut adalah sesuatu yang lain yang dikompilasi dengan C++/WinRT 1.0.
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
Ini adalah kesalahan pada beberapa level. Kami memiliki dua jumlah referensi yang berbeda untuk objek yang sama. Windows Runtime (dan sebelumnya COM klasik) didasarkan pada perhitungan referensi intrinsik yang tidak kompatibel dengan std::shared_ptr. std::shared_ptr, tentu saja memiliki banyak aplikasi yang valid; tetapi sama sekali tidak perlu ketika Anda berbagi objek Windows Runtime (dan COM klasik). Terakhir, ini juga dikompilasi dengan C++/WinRT 1.0.
auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.
Ini lagi-lagi agak dipertanyakan. Kepemilikan unik berlawanan dengan umur bersama dari jumlah referensi intrinsik MyStringable.
Solusi dengan C++/WinRT 2.0
Dengan C++/WinRT 2.0, semua upaya ini untuk langsung mengalokasikan jenis implementasi menyebabkan kesalahan kompilator. Itu adalah jenis kesalahan terbaik, dan jauh lebih baik daripada bug runtime misterius.
Setiap kali Anda perlu membuat implementasi, Anda cukup menggunakan winrt::make atau winrt::make_self, seperti yang ditunjukkan di atas. Dan sekarang, jika Anda lupa melakukannya, maka Anda akan disambut dengan kesalahan kompilator yang menyinggung hal ini dengan referensi ke fungsi abstrak bernama use_make_function_to_create_this_object. Ini bukan tepat static_assert; tapi hampir sama. Namun, ini adalah cara yang paling dapat diandalkan untuk mendeteksi semua kesalahan yang dijelaskan.
Ini berarti bahwa kita perlu menempatkan beberapa batasan kecil pada implementasi. Mengingat bahwa kita mengandalkan tidak adanya penimpaan untuk mendeteksi alokasi langsung, templat fungsi winrt::make harus entah bagaimana memenuhi fungsi virtual abstrak dengan penimpaan. Ini dilakukan dengan menurunkan dari implementasi menggunakan kelas final yang menyediakan fungsi penimpaan. Ada beberapa hal yang perlu diamati tentang proses ini.
Pertama, fungsi virtual hanya ada dalam build debug. Yang berarti bahwa deteksi tidak akan memengaruhi ukuran vtable dalam build yang telah dioptimalkan.
Kedua, karena kelas turunan yang digunakan oleh winrt::make adalah final, ini berarti bahwa devirtualisasi apa pun yang dapat disimpulkan pengoptimal akan terjadi bahkan jika Anda sebelumnya memilih untuk tidak menandai kelas implementasi Anda sebagai final. Jadi itu adalah perbaikan. Sebaliknya, implementasi Anda tidak dapatfinal. Sekali lagi, itu tidak menjadi masalah karena jenis yang dibuat akan selalu final.
Ketiga, tidak ada yang mencegah Anda menandai fungsi virtual apa pun dalam implementasi Anda sebagai final. Tentu saja, C++/WinRT sangat berbeda dari COM klasik dan implementasi seperti WRL, di mana segala sesuatu tentang implementasi Anda cenderung virtual. Di C++/WinRT, pengiriman virtual terbatas pada antarmuka biner aplikasi (ABI) (yang selalu final), dan metode implementasi Anda mengandalkan polimorfisme kompilasi atau statis. Itu menghindari polimorfisme runtime yang tidak perlu, dan juga berarti bahwa ada sedikit alasan berharga untuk fungsi virtual dalam implementasi C++/WinRT Anda. Yang merupakan hal yang sangat baik, dan mengarah ke inlining yang jauh lebih dapat diprediksi.
Keempat, karena winrt::make menyuntikkan kelas turunan, implementasi Anda tidak dapat memiliki destruktor privat. Destruktor privat sangat populer dalam implementasi COM klasik karena sekali lagi, semuanya bersifat virtual, dan umum untuk berurusan langsung dengan pointer asli dan dengan demikian mudah untuk secara tidak sengaja memanggil delete alih-alih Release. C++/WinRT berupaya keras untuk mempersulit Anda dalam menangani pointer mentah secara langsung. Dan Anda harus benar-benar keluar dari jalan Anda untuk mendapatkan pointer mentah di C++/WinRT yang berpotensi Anda hubungi delete. Semantik nilai berarti Anda berhadapan dengan nilai dan referensi; dan jarang dengan pointer.
Jadi, C++/WinRT menantang gagasan kami yang telah ditentukan sebelumnya tentang apa artinya menulis kode COM klasik. Dan itu sangat masuk akal karena WinRT bukan COM klasik. COM klasik adalah bahasa perakitan Windows Runtime. Seharusnya bukan kode yang Anda tulis setiap hari. Sebaliknya, C++/WinRT membuat Anda menulis kode yang lebih seperti C++modern, dan jauh lebih sedikit seperti COM klasik.