Katakanlah saya memiliki yang berikut di class X
mana saya ingin mengembalikan akses ke anggota internal:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
Dua fungsi anggota X::Z()
dan X::Z() const
memiliki kode yang identik di dalam kawat gigi. Ini adalah kode duplikat dan dapat menyebabkan masalah pemeliharaan untuk fungsi yang panjang dengan logika yang kompleks .
Apakah ada cara untuk menghindari duplikasi kode ini?
Jawaban:
Untuk penjelasan terperinci, silakan lihat tajuk "Hindari Duplikasi dalam
const
danconst
Fungsi Non- Anggota," pada halaman. 23, dalam Butir 3 "Gunakanconst
jika memungkinkan," dalam C ++ Efektif , 3d ed oleh Scott Meyers, ISBN-13: 9780321334879.Inilah solusi Meyers (disederhanakan):
Dua gips dan pemanggilan fungsi mungkin jelek tapi itu benar. Meyers memiliki penjelasan menyeluruh mengapa.
sumber
get()const
mengembalikan sesuatu yang didefinisikan sebagai objek const, maka seharusnya tidak ada versi non-constget()
sama sekali. Sebenarnya pemikiran saya tentang hal ini telah berubah dari waktu ke waktu: solusi template adalah satu-satunya cara untuk menghindari duplikasi dan mendapatkan comp-check const-correctness, jadi secara pribadi saya tidak akan lagi menggunakanconst_cast
untuk menghindari duplikasi kode, saya akan memilih antara menempatkan kode yang dibohongi ke dalam templat fungsi atau membiarkannya ditiru.template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }
dantemplate<typename T> T& variable(const T& _) { return const_cast<T&>(_); }
. Maka Anda dapat melakukan:return variable(constant(*this).get());
Ya, mungkin untuk menghindari duplikasi kode. Anda perlu menggunakan fungsi anggota konst untuk memiliki logika dan meminta fungsi anggota non-konst untuk memanggil fungsi anggota konst dan melemparkan kembali nilai kembali ke referensi non-const (atau pointer jika fungsi mengembalikan pointer):
CATATAN: Adalah penting bahwa Anda TIDAK menempatkan logika dalam fungsi non-const dan meminta fungsi const memanggil fungsi non-const - ini dapat mengakibatkan perilaku yang tidak terdefinisi. Alasannya adalah bahwa instance kelas konstan akan digunakan sebagai instance yang tidak konstan. Fungsi non-const member dapat secara tidak sengaja memodifikasi kelas, yang menyatakan standar C ++ akan menghasilkan perilaku yang tidak terdefinisi.
sumber
C ++ 17 telah memperbarui jawaban terbaik untuk pertanyaan ini:
Ini memiliki kelebihan yaitu:
volatile
secara tidak sengaja, tetapivolatile
merupakan kualifikasi langka)Jika Anda ingin pergi rute deduksi penuh maka itu dapat dicapai dengan memiliki fungsi pembantu
Sekarang Anda bahkan tidak dapat mengacaukan
volatile
, dan penggunaannya terlihat sepertisumber
f()
kembaliT
sebagai gantinyaT&
.f()
pengembalianT
, kami tidak ingin memiliki dua kelebihan,const
versi saja sudah cukup.shared_ptr
. Jadi apa yang saya benar-benar diperlukan adalah sesuatu sepertias_mutable_ptr
yang terlihat hampir identik denganas_mutable
di atas, kecuali bahwa dibutuhkan dan kembali sebuahshared_ptr
dan penggunaanstd::const_pointer_cast
bukanconst_cast
.T const*
maka ini akan mengikatT const* const&&
daripada mengikatT const* const&
(setidaknya dalam pengujian saya itu lakukan). Saya harus menambahkan overloadT const*
sebagai tipe argumen untuk metode yang mengembalikan pointer.Saya pikir solusi Scott Meyers dapat ditingkatkan di C ++ 11 dengan menggunakan fungsi pembantu tempate. Ini membuat niat menjadi lebih jelas dan dapat digunakan kembali untuk banyak pengambil lainnya.
Fungsi pembantu ini dapat digunakan dengan cara berikut.
Argumen pertama selalu merupakan penunjuk ini. Yang kedua adalah pointer ke fungsi anggota untuk memanggil. Setelah itu sejumlah argumen tambahan yang sewenang-wenang dapat diajukan sehingga mereka dapat diteruskan ke fungsi. Ini membutuhkan C ++ 11 karena templat variadic.
sumber
std::remove_bottom_const
ikutstd::remove_const
.const_cast
. Anda bisa membuatgetElement
template sendiri, dan menggunakan sifat tipe di dalam untukmpl::conditional
tipe yang Anda butuhkan, sepertiiterator
atauconstiterator
jika diperlukan. Masalah sebenarnya adalah bagaimana membuat versi const dari suatu metode ketika bagian tanda tangan ini tidak dapat di-templatize?std::remove_const<int const&>
isint const &
(hapusconst
kualifikasi tingkat atas ), maka dari itulah senamNonConst<T>
dalam jawaban ini. Putatifstd::remove_bottom_const
dapat menghapusconst
kualifikasi tingkat bawah , dan melakukan apa yangNonConst<T>
dilakukan di sini:std::remove_bottom_const<int const&>::type
=>int&
.getElement
kelebihan beban. Kemudian pointer fungsi tidak dapat diselesaikan tanpa memberikan parameter template secara eksplisit. Mengapa?likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }
Lengkap: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83Sedikit lebih bertele-tele daripada Meyers, tetapi saya mungkin melakukan ini:
Metode pribadi memiliki properti yang tidak diinginkan yang mengembalikan non-const Z & untuk turunan const, yang mengapa itu pribadi. Metode privat dapat memecah invarian dari antarmuka eksternal (dalam hal ini invarian yang diinginkan adalah "objek const tidak dapat dimodifikasi melalui referensi yang diperoleh melaluinya ke objek yang memiliki-a").
Perhatikan bahwa komentar adalah bagian dari pola - antarmuka _getZ menentukan bahwa tidak pernah valid untuk menyebutnya (selain dari pengakses, tentu saja): bagaimanapun juga tidak ada manfaat yang mungkin untuk melakukannya, karena 1 karakter lagi untuk diketik dan tidak akan menghasilkan kode yang lebih kecil atau lebih cepat. Memanggil metode ini sama dengan memanggil salah satu accessors dengan const_cast, dan Anda juga tidak ingin melakukannya. Jika Anda khawatir akan membuat kesalahan menjadi jelas (dan itu adalah tujuan yang adil), sebut saja itu const_cast_getZ alih-alih _getZ.
Omong-omong, saya menghargai solusi Meyers. Saya tidak punya keberatan filosofis untuk itu. Namun, secara pribadi, saya lebih suka sedikit pengulangan terkontrol, dan metode pribadi yang hanya boleh disebut dalam keadaan tertentu yang dikontrol ketat, daripada metode yang terlihat seperti derau garis. Pilih racun Anda dan tetap menggunakannya.
[Sunting: Kevin dengan tepat menunjukkan bahwa _getZ mungkin ingin memanggil metode lebih lanjut (katakanlah generateZ) yang merupakan spesialisasi khusus dengan cara getZ yang sama. Dalam kasus ini, _getZ akan melihat const Z & dan harus const_cast sebelum kembali. Itu masih aman, karena accessor boiler mengatur segalanya, tetapi tidak terlalu jelas bahwa itu aman. Lebih jauh, jika Anda melakukan itu dan kemudian mengubah generateZ untuk selalu mengembalikan const, maka Anda juga perlu mengubah getZ untuk selalu mengembalikan const, tetapi kompiler tidak akan memberi tahu Anda bahwa Anda melakukannya.
Poin terakhir tentang kompiler juga berlaku untuk pola yang disarankan Meyers, tetapi poin pertama tentang const_cast yang tidak jelas tidak. Jadi pada keseimbangan saya berpikir bahwa jika _getZ ternyata membutuhkan const_cast untuk nilai pengembaliannya, maka pola ini kehilangan banyak nilainya dibandingkan dengan Meyers. Karena itu juga mengalami kerugian dibandingkan dengan Meyers, saya pikir saya akan beralih ke miliknya dalam situasi itu. Refactoring dari satu ke yang lain itu mudah - tidak memengaruhi kode lain yang valid di kelas, karena hanya kode yang tidak valid dan panggilan boilerplate _getZ.]
sumber
something
dalam_getZ()
fungsi adalah variabel instan? Kompiler (atau paling tidak beberapa kompiler) akan mengeluh bahwa karena_getZ()
const, variabel instan apa pun yang dirujuk di dalamnya adalah const juga. Makasomething
akan menjadi const (itu akan menjadi tipeconst Z&
) dan tidak dapat dikonversi keZ&
. Dalam pengalaman saya (memang agak terbatas), sebagian besar waktusomething
adalah variabel instan dalam kasus seperti ini.const_cast
. Itu dimaksudkan untuk menjadi place-holder untuk kode yang diperlukan untuk mendapatkan pengembalian non-const dari objek const, bukan sebagai place-holder untuk apa yang seharusnya ada pada pengambil duplikat. Jadi "sesuatu" bukan hanya variabel instan.Pertanyaan yang bagus dan jawaban yang bagus. Saya punya solusi lain, yang tidak menggunakan gips:
Namun, itu memiliki keburukan yang membutuhkan anggota statis dan kebutuhan menggunakan
instance
variabel di dalamnya.Saya tidak mempertimbangkan semua kemungkinan implikasi (negatif) dari solusi ini. Tolong beri tahu saya jika ada.
sumber
auto get(std::size_t i) -> auto(const), auto(&&)
. Kenapa '&&'? Ahh, jadi saya bisa bilang:auto foo() -> auto(const), auto(&&) = delete;
this
kata kunci. Saya menyarankantemplate< typename T > auto myfunction(T this, t args) -> decltype(ident)
kata kunci ini akan dikenali sebagai argumen instance objek implisit dan biarkan kompiler mengenali bahwa fungsi saya adalah anggota atauT
.T
akan disimpulkan secara otomatis di situs panggilan, yang akan selalu menjadi tipe kelas, tetapi dengan kualifikasi cv gratis.const_cast
) untuk memungkinkan untuk kembaliiterator
danconst_iterator
.static
dapat dilakukan pada ruang lingkup file alih-alih ruang lingkup kelas. :-)Anda juga bisa menyelesaikannya dengan templat. Solusi ini sedikit jelek (tetapi keburukan disembunyikan dalam file .cpp) tetapi memang memberikan pemeriksaan kompiler dari constness, dan tidak ada duplikasi kode.
file .h:
file .cpp:
Kerugian utama yang dapat saya lihat adalah bahwa karena semua implementasi kompleks dari metode ini ada dalam fungsi global, Anda juga perlu menghubungi anggota X menggunakan metode publik seperti GetVector () di atas (yang selalu perlu menjadi versi const dan non-const) atau Anda dapat menjadikan fungsi ini sebagai teman. Tapi saya tidak suka teman.
[Sunting: menghapus termasuk yang tidak dibutuhkan dari cstdio yang ditambahkan selama pengujian.]
sumber
const_cast
(yang secara tidak sengaja dapat digunakan untuk canst sesuatu yang sebenarnya seharusnya menjadi const untuk sesuatu yang tidak).Bagaimana dengan memindahkan logika ke metode pribadi, dan hanya melakukan hal-hal "dapatkan referensi dan kembalikan" di dalam getter? Sebenarnya, saya akan cukup bingung tentang static dan const cast di dalam fungsi pengambil sederhana, dan saya akan menganggap itu jelek kecuali untuk keadaan yang sangat langka!
sumber
Apakah curang menggunakan preprocessor?
Ini tidak semewah templat atau gips, tetapi itu membuat niat Anda ("dua fungsi ini harus identik") cukup eksplisit.
sumber
Mengejutkan bagi saya bahwa ada begitu banyak jawaban yang berbeda, namun hampir semuanya bergantung pada keajaiban templat berat. Template itu kuat, tetapi terkadang makro mengalahkannya dalam keringkasan. Fleksibilitas maksimum sering dicapai dengan menggabungkan keduanya.
Saya menulis makro
FROM_CONST_OVERLOAD()
yang dapat ditempatkan di fungsi non-const untuk memanggil fungsi const.Contoh penggunaan:
Implementasi yang sederhana dan dapat digunakan kembali:
Penjelasan:
Seperti yang diposting dalam banyak jawaban, pola umum untuk menghindari duplikasi kode dalam fungsi non-const member adalah ini:
Banyak pelat baja ini dapat dihindari menggunakan inferensi tipe. Pertama,
const_cast
dapat diringkas dalamWithoutConst()
, yang menyimpulkan jenis argumennya dan menghapus const-kualifikasi. Kedua, pendekatan yang sama dapat digunakanWithConst()
untuk mengkualifikasithis
pointer, yang memungkinkan memanggil metode const-overloaded.Sisanya adalah makro sederhana yang mengawali panggilan dengan kualifikasi yang benar
this->
dan menghilangkan const dari hasilnya. Karena ekspresi yang digunakan dalam makro hampir selalu merupakan panggilan fungsi sederhana dengan argumen 1: 1 yang diteruskan, kelemahan makro seperti beberapa evaluasi tidak muncul. Ellipsis dan__VA_ARGS__
juga bisa digunakan, tetapi seharusnya tidak diperlukan karena koma (seperti pemisah argumen) terjadi di dalam tanda kurung.Pendekatan ini memiliki beberapa manfaat:
FROM_CONST_OVERLOAD( )
const_iterator
,std::shared_ptr<const T>
, dll). Untuk ini, cukup kelebihanWithoutConst()
untuk jenis yang sesuai.Keterbatasan: solusi ini dioptimalkan untuk skenario di mana kelebihan non-const melakukan persis sama dengan overload const, sehingga argumen dapat diteruskan 1: 1. Jika logika Anda berbeda dan Anda tidak memanggil versi const melalui
this->Method(args)
, Anda dapat mempertimbangkan pendekatan lain.sumber
Bagi mereka (seperti saya) yang
di sini adalah mengambil lain:
Ini pada dasarnya adalah campuran dari jawaban dari @Pait, @DavidStone dan @ sh1 ( EDIT : dan peningkatan dari @cdhowie). Apa yang ditambahkan ke tabel adalah Anda hanya mendapatkan satu baris kode tambahan yang hanya memberi nama fungsi (tetapi tidak ada argumen atau duplikasi tipe pengembalian):
Catatan: gcc gagal mengkompilasi ini sebelum 8.1, clang-5 dan ke atas serta MSVC-19 senang (menurut kompiler explorer ).
sumber
decltype()
s juga digunakanstd::forward
pada argumen untuk memastikan bahwa kami menggunakan jenis pengembalian yang benar dalam kasus di mana kami memiliki kelebihanget()
yang menggunakan berbagai jenis referensi?NON_CONST
menyimpulkan makro jenis kembali tidak benar danconst_cast
s dengan jenis yang salah karena kurangnya forwarding didecltype(func(a...))
jenis. Menggantinya dengandecltype(func(std::forward<T>(a)...))
memecahkan ini . (Hanya ada kesalahan linker karena saya tidak pernah mendefinisikanX::get
kelebihan yang dinyatakan .)Berikut adalah versi C ++ 17 dari fungsi pembantu statis templat, dengan dan uji SFINAE opsional.
Versi lengkap: https://godbolt.org/z/mMK4r3
sumber
Saya datang dengan makro yang menghasilkan pasangan fungsi const / non-const secara otomatis.
Lihat akhir jawaban untuk implementasi.
Argumen
MAYBE_CONST
diduplikasi. Dalam salinan pertama,CV
diganti dengan apa-apa; dan di salinan kedua diganti denganconst
.Tidak ada batasan berapa kali
CV
dapat muncul dalam argumen makro.Namun ada sedikit ketidaknyamanan. Jika
CV
muncul di dalam tanda kurung, sepasang tanda kurung ini harus diawali denganCV_IN
:Penerapan:
Implementasi Pra-C ++ 20 yang tidak mendukung
CV_IN
:sumber
Biasanya, fungsi anggota yang Anda perlukan versi const dan non-const adalah getter dan setter. Sebagian besar waktu mereka satu-baris sehingga duplikasi kode tidak menjadi masalah.
sumber
Saya melakukan ini untuk seorang teman yang berhak membenarkan penggunaan
const_cast
... tidak mengetahuinya saya mungkin akan melakukan sesuatu seperti ini (tidak benar-benar elegan):sumber
Saya akan menyarankan templat fungsi statis pembantu pribadi, seperti ini:
sumber
Artikel DDJ ini menunjukkan cara menggunakan spesialisasi templat yang tidak mengharuskan Anda untuk menggunakan const_cast. Untuk fungsi sederhana seperti itu sebenarnya tidak diperlukan.
boost :: any_cast (pada satu titik, tidak lagi) menggunakan const_cast dari versi const yang memanggil versi non-const untuk menghindari duplikasi. Anda tidak bisa memaksakan const semantik pada versi non-const, jadi Anda harus sangat berhati-hati dengan itu.
Pada akhirnya beberapa kode duplikasi adalah baik-baik saja selama dua cuplikan secara langsung di atas satu sama lain.
sumber
Untuk menambah solusi jwfearn dan kevin yang disediakan, inilah solusi yang sesuai ketika fungsi mengembalikan shared_ptr:
sumber
Tidak menemukan apa yang saya cari, jadi saya menggulung beberapa sendiri ...
Yang ini sedikit bertele-tele, tetapi memiliki keuntungan menangani banyak metode kelebihan nama yang sama (dan tipe pengembalian) sekaligus:
Jika Anda hanya memiliki satu
const
metode per nama, tetapi masih banyak metode untuk digandakan, maka Anda mungkin lebih suka ini:Sayangnya ini rusak segera setelah Anda mulai kelebihan nama (daftar argumen argumen fungsi pointer tampaknya belum terselesaikan pada titik itu, sehingga tidak dapat menemukan kecocokan untuk argumen fungsi). Meskipun Anda dapat membuat templat jalan keluar dari itu,
Tetapi argumen referensi ke
const
metode gagal untuk mencocokkan dengan argumen yang tampaknya bernilai dengan templat dan rusak.Tidak yakin kenapa.Inilah sebabnya .sumber