Mengapa penggunaan tupel di C ++ tidak lebih umum?


124

Mengapa tampaknya tidak ada yang menggunakan tupel di C ++, baik Perpustakaan Boost Tuple atau perpustakaan standar untuk TR1? Saya telah membaca banyak kode C ++, dan sangat jarang saya melihat penggunaan tupel, tetapi saya sering melihat banyak tempat di mana tupel akan memecahkan banyak masalah (biasanya mengembalikan banyak nilai dari fungsi).

Tuple memungkinkan Anda melakukan semua jenis hal keren seperti ini:

tie(a,b) = make_tuple(b,a); //swap a and b

Itu pasti lebih baik dari ini:

temp=a;
a=b;
b=temp;

Tentu saja Anda selalu bisa melakukan ini:

swap(a,b);

Tetapi bagaimana jika Anda ingin merotasi tiga nilai? Anda dapat melakukan ini dengan tupel:

tie(a,b,c) = make_tuple(b,c,a);

Tupel juga mempermudah pengembalian beberapa variabel dari suatu fungsi, yang mungkin merupakan kasus yang jauh lebih umum daripada menukar nilai. Menggunakan referensi untuk mengembalikan nilai tentu tidak terlalu elegan.

Apakah ada kekurangan besar pada tupel yang tidak saya pikirkan? Jika tidak, mengapa jarang digunakan? Apakah mereka lebih lambat? Atau hanya karena orang tidak terbiasa dengan mereka? Apakah ide yang bagus untuk menggunakan tupel?


17
1 untuk trik bertukar tupel pintar :)
kizzx2

10
a = a ^ b; b = a ^ b; a = a ^ b;
Gerardo Marset

3
Tupel IMO cocok digunakan dalam bahasa pengetikan lemah atau bahasa yang merupakan struktur asli. Misalnya di Python atau PHP mereka hanya membuat hidup lebih mudah, sementara di C ++ ada terlalu banyak pengetikan (untuk membuatnya dari template) dan terlalu sedikit manfaat.
dokumen

4
Komentar untuk OP: Saya pikir jawaban yang diterima saat ini sudah usang hingga salah secara faktual. Anda mungkin ingin mempertimbangkan kembali pilihan jawaban yang diterima.
ulidtko

8
@GerardoMarset Apakah Anda serius?
Santo

Jawaban:


43

Karena itu belum standar. Apa pun yang tidak standar memiliki rintangan yang jauh lebih tinggi. Potongan Boost telah menjadi populer karena programmer berteriak-teriak untuk itu. (hash_map melompat ke pikiran). Tapi sementara tuple berguna, itu bukan kemenangan yang luar biasa dan jelas sehingga orang-orang peduli dengannya.


1
Orang-orang tampaknya menggunakan bagian Boost lain seperti orang gila. Meskipun secara umum peta hash jauh lebih berguna daripada tupel.
Zifre

Saya tidak tahu secara spesifik tentang apa yang Anda lihat, tetapi saya menduga bahwa bagian yang digunakan orang secara gila-gilaan adalah fitur yang benar-benar mereka inginkan. Jadi (sekali lagi, menebak) popularitas peta hash, penunjuk yang dihitung, dan sejenisnya. Tupel memang berguna, tetapi itu bukan sesuatu yang melompat keluar untuk mengisi lubang. Imbalannya tidak jelas. Seberapa sering Anda perlu memutar dengan tepat N objek? (Berbeda dengan perlu memutar vektor panjang sewenang-wenang). Dan orang-orang terbiasa meneruskan nilai kembali dengan referensi, atau mengembalikan kelas kecil atau struct.
Alan De Smet

20
Ini sebenarnya adalah bagian dari standar C ++ 11 sekarang: en.cppreference.com/w/cpp/utility/tuple
Roman Susi

124

Jawaban sinisnya adalah banyak orang memprogram dalam C ++, tetapi tidak memahami dan / atau menggunakan fungsionalitas tingkat yang lebih tinggi. Kadang-kadang karena mereka tidak diperbolehkan, tetapi banyak yang tidak mencoba (atau bahkan mengerti).

Sebagai contoh non-boost: berapa banyak orang yang menggunakan fungsionalitas yang ditemukan <algorithm>?

Dengan kata lain, banyak programmer C ++ hanyalah programmer C yang menggunakan kompiler C ++, dan mungkin std::vectordan std::list. Itulah salah satu alasan mengapa penggunaan boost::tupletidak lebih umum.


18
-1 dari saya karena programmer C ++ tidak sebodoh jawaban ini yang membuat mereka terdengar.
pengguna541686

5
@Mehrdad Setelah melihat-lihat banyak kode C ++, baik komersial maupun tidak, membaca banyak materi C ++, menurut saya cukup aman untuk mengatakan bahwa sebagian besar pengembang "C ++" hanyalah pengembang C yang tidak bisa mendapatkan murni Kompiler C. Template, misalnya, hampir seluruhnya hilang dari sebagian besar materi (sesuatu yang saya pelajari untuk sangat saya sukai). Peretasan makro yang aneh sering terjadi dan namespace sangat jarang digunakan.
jelas

5
Jawaban yang tidak masuk akal. Jika seseorang membutuhkannya, mereka akan menyusulnya. Mereka tidak dibutuhkan; jadi mereka tidak digunakan. Mengatakan bahwa mereka tidak digunakan karena tidak mudah dimengerti itu buruk.
Michael Chourdakis

9
@Michael Nonsense komentar. Tidak ada yang diperlukan dalam pemrograman setelah Anda mendapatkan subset bahasa Turing yang lengkap. Kurangnya penggunaan tentu tidak berarti bahwa setiap orang memahami konstruksi C ++ tingkat yang lebih tinggi dan memilih untuk tidak menggunakannya.
Trey Jackson

4
Tbh Saya TIDAK PERNAH membutuhkan std :: tuple di luar metaprogramming template variadic. Tidak ada gunanya dalam hidup di mana saya duduk dengan wajah sedih berpikir "Seandainya saya memiliki tupel itu". Faktanya ketika saya melihat tupel saya berpikir "Untuk apa orang waras membutuhkan mereka (saya tidak akan menganggap diri saya waras)". Orang-orang di luar meta-pemrograman tampaknya menggunakannya sebagai "Anonymous Struct" yang sangat jelek dan menunjukkan tidak adanya kualitas kode dan pemeliharaan yang kuat di kepala mereka.
Santo

23

Sintaks C ++ tuple bisa lebih bertele-tele daripada yang diinginkan kebanyakan orang.

Mempertimbangkan:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

Jadi jika Anda ingin menggunakan tupel secara ekstensif, Anda bisa mendapatkan tuple typedef di mana-mana atau Anda mendapatkan nama tipe yang sangat panjang di mana-mana. Saya suka tuple. Saya menggunakannya bila perlu. Tapi biasanya terbatas pada beberapa situasi, seperti indeks elemen-N atau saat menggunakan multimaps untuk mengikat pasangan iterator rentang. Dan biasanya dalam ruang lingkup yang sangat terbatas.

Semuanya terlihat sangat jelek dan meretas jika dibandingkan dengan sesuatu seperti Haskell atau Python. Ketika C ++ 0x sampai di sini dan kita mendapatkan kata kunci 'otomatis' tupel akan mulai terlihat jauh lebih menarik.

Kegunaan tupel berbanding terbalik dengan jumlah penekanan tombol yang diperlukan untuk mendeklarasikan, mengemas, dan mengekstraknya.


Kebanyakan orang akan melakukan "using namespace boost;" dan tidak harus mengetik boost ::. Saya tidak berpikir mengetik tupel itu banyak masalah. Yang mengatakan, saya pikir Anda ada benarnya. auto mungkin membuat lebih banyak orang mulai menggunakan tupel.
Zifre

2
@Zifre: masalahnya adalah Anda tidak boleh melakukan "menggunakan namespace X" dalam file header karena memaksa polusi namespace dan menumbangkan namespace.
Tuan Fooz

1
Ah, ya, saya lupa tentang header. Namun di dalam kode program, Anda tidak perlu khawatir. Dan begitu kita memiliki C ++ 0x, kita bisa menggunakan auto yang seharusnya menghilangkan banyak pengetikan.
Zifre

19
Apa hanya aku? Saya tidak berpikir menyimpan mengetik 7 karakter "boost ::" yang dia maksud, melainkan 33 karakter lainnya . Itu banyak sekali pengetikan nama kelas, terutama jika nama kelas itu juga memiliki cakupan ruang nama. Gunakan boost :: tuple <std :: string, std :: set <std :: string>, std :: vector <My :: Scoped :: LongishTypeName>> sebagai contoh konyol.
Ogre Mazmur33

10

Bagi saya, itu kebiasaan, tangan ke bawah: Tuple tidak memecahkan masalah baru bagi saya, hanya beberapa yang sudah bisa saya tangani dengan baik. Bertukar nilai masih terasa lebih mudah dengan cara lama - dan, yang lebih penting, saya tidak terlalu memikirkan cara menukar "lebih baik". Ini cukup bagus apa adanya.

Secara pribadi, saya tidak berpikir tupel adalah solusi yang bagus untuk mengembalikan banyak nilai - kedengarannya seperti pekerjaan untuk structs.


4
'Saya tidak benar-benar berpikir tentang bagaimana menukar "lebih baik."' - Ketika saya menulis kode, saya menulis bug. Mengurangi kompleksitas kode mengurangi jumlah bug yang saya tulis. Saya benci membuat bug yang sama berulang kali. Ya, saya benar-benar memikirkan tentang cara <strike> menukar </> kode dengan lebih baik . Lebih sedikit bagian yang bergerak (LOC, variabel temp, pengenal salah ketik), lebih banyak kode yang dapat dibaca; Kode Baik.
lihat

Setuju. Kelas yang dibungkus dengan penunjuk otomatis atau penunjuk cerdas adalah jenis-simpan. Saya telah menggunakan tupel sekali, tetapi kemudian menulis ulang kode tersebut menggunakan kelas. retValue.state lebih jelas daripada retValue.get <0> ().
Valentin Heinitz

1
@sehe: Menulis kode yang lebih baik dan lebih mudah dibaca adalah tujuan saya juga. Menambahkan lebih banyak jenis sintaks memiliki biaya, dan saya tidak percaya "pertukaran yang lebih baik" membenarkan beban mental untuk memikirkan lebih banyak jenis sintaks untuk setiap baris kode yang Anda baca.
ojrac

8

Tetapi bagaimana jika Anda ingin merotasi tiga nilai?

swap(a,b);
swap(b,c);  // I knew those permutation theory lectures would come in handy.

OK, jadi dengan 4 nilai dll, akhirnya n-tuple menjadi kode yang lebih sedikit dari n-1 swap. Dan dengan default swap, ini melakukan 6 tugas, bukan 4 yang Anda miliki jika Anda menerapkan template tiga siklus sendiri, meskipun saya berharap kompilator akan menyelesaikannya untuk tipe sederhana.

Anda dapat menemukan skenario di mana swap tidak berguna atau tidak sesuai, misalnya:

tie(a,b,c) = make_tuple(b*c,a*c,a*b);

agak canggung untuk dibuka.

Intinya adalah, bagaimanapun, ada cara yang diketahui untuk menangani situasi paling umum yang baik untuk tupel, dan karenanya tidak ada urgensi yang besar untuk mengambil tupel. Jika tidak ada yang lain, saya tidak yakin bahwa:

tie(a,b,c) = make_tuple(b,c,a);

tidak membuat 6 salinan, membuatnya sama sekali tidak cocok untuk beberapa jenis (koleksi menjadi yang paling jelas). Jangan ragu untuk meyakinkan saya bahwa tuple adalah ide yang bagus untuk tipe "besar", dengan mengatakan ini tidak begitu :-)

Untuk mengembalikan beberapa nilai, tupel sempurna jika nilainya memiliki tipe yang tidak kompatibel, tetapi beberapa orang tidak menyukainya jika pemanggil bisa mendapatkannya dalam urutan yang salah. Beberapa orang sama sekali tidak menyukai beberapa nilai pengembalian, dan tidak ingin mendorong penggunaannya dengan membuatnya lebih mudah. Beberapa orang lebih suka struktur bernama untuk parameter masuk dan keluar, dan mungkin tidak dapat dibujuk dengan tongkat baseball untuk menggunakan tupel. Tidak ada perhitungan rasa.


1
Anda pasti tidak ingin menukar vektor dengan tupel. Saya pikir menukar tiga elemen tentu lebih jelas dengan tupel daripada dengan dua swap. Adapun beberapa nilai pengembalian, parameter keluar jahat, struct adalah pengetikan tambahan, dan pasti ada kasus di mana beberapa nilai pengembalian diperlukan.
Zifre

tahu mengapa Anda menggunakan tupel (dan saya tahu mengapa saya akan menggunakannya jika ada kesempatan, meskipun saya rasa tidak pernah). Saya menebak mengapa orang lain tidak menggunakannya bahkan jika mereka menyadarinya. misalnya karena mereka tidak setuju dengan "out params are evil" ...
Steve Jessop

Apakah Anda tahu apakah kita bisa mengganti "tie (a, b, c) = make_tuple (b, c, a);" dengan "dasi (a, b, c) = dasi (b, c, a);" ?
Rexxar

2
Tie (well, tier secara teknis) adalah tupel yang dibuat dengan referensi non-const. Saya tidak dapat menemukan dokumentasi pendorong yang mengatakan apa yang menjamin operator = dan konstruktor salinan untuk dasi / tuple dibuat ketika beberapa referensi yang terlibat memiliki referensi yang sama. Tapi itulah yang perlu Anda ketahui. Penerapan naif operator = jelas bisa menjadi sangat salah ...
Steve Jessop

1
@Steve: Dan karena hubungan adalah tentang mencegah salinan (mereka harus bekerja untuk jenis yang tidak dapat disalin; perhatikan bahwa LHS mungkin sesuatu yang sama sekali lain) memang semuanya akan menjadi sangat salah (pikirkan objek kelas non-POD). Bayangkan saja bagaimana Anda akan menulis logika yang sama tanpa menggunakan temps.
lihat

7

Seperti yang ditunjukkan banyak orang, tupel tidak begitu berguna seperti fitur lainnya.

  1. Gimmick yang bertukar dan berputar hanyalah tipuan. Mereka benar-benar membingungkan bagi mereka yang belum pernah melihatnya sebelumnya, dan karena hampir semua orang, tipu muslihat ini hanyalah praktik rekayasa perangkat lunak yang buruk.

  2. Mengembalikan beberapa nilai menggunakan tupel jauh lebih sedikit mendokumentasikan diri sendiri daripada alternatif - mengembalikan tipe bernama atau menggunakan referensi bernama. Tanpa pendokumentasian sendiri ini, mudah untuk mengacaukan urutan nilai yang dikembalikan, jika keduanya dapat diubah satu sama lain, dan tidak lebih bijak.


6

Tidak semua orang dapat menggunakan boost, dan TR1 belum tersedia secara luas.


3
Banyak orang menggunakan Boost. Orang-orang itu juga bisa menggunakan tupel.
Zifre

3
Anda bertanya mengapa orang tidak menggunakannya, dan saya memberikan satu jawaban.
Brian Neal

2
Untuk pemilih bawah: Saya kebetulan bekerja di tempat di mana secara politik tidak mungkin untuk menggunakan dorongan, dan bahkan pada tanggal ini, rantai alat kompilator yang kita gunakan (untuk sistem tertanam) tidak memiliki dukungan TR1 / C ++ 11.
Brian Neal

5

Saat menggunakan C ++ pada sistem tersemat, menarik pustaka Boost menjadi rumit. Mereka berpasangan satu sama lain, sehingga ukuran perpustakaan bertambah. Anda mengembalikan struktur data atau menggunakan penerusan parameter alih-alih tupel. Ketika mengembalikan tupel dengan Python, struktur data berada dalam urutan dan jenis nilai yang dikembalikan itu tidak eksplisit.


5

Anda jarang melihatnya karena kode yang dirancang dengan baik biasanya tidak membutuhkannya- tidak banyak kasus di alam liar di mana menggunakan struct anonim lebih baik daripada menggunakan yang bernama. Karena semua tupel benar-benar mewakili sebuah struct anonim, kebanyakan pembuat kode dalam banyak situasi hanya pergi dengan yang asli.

Katakanlah kita memiliki fungsi "f" di mana pengembalian tupel mungkin masuk akal. Sebagai aturan umum, fungsi seperti itu biasanya cukup rumit sehingga bisa gagal.

Jika "f" DAPAT gagal, Anda memerlukan pengembalian status - bagaimanapun juga, Anda tidak ingin pemanggil harus memeriksa setiap parameter untuk mendeteksi kegagalan. "f" mungkin cocok dengan pola:

struct ReturnInts ( int y,z; }
bool f(int x, ReturnInts& vals);

int x = 0;
ReturnInts vals;
if(!f(x, vals)) {
    ..report error..
    ..error handling/return...
}

Itu tidak bagus, tapi lihat betapa jelek alternatifnya. Perhatikan bahwa saya masih membutuhkan nilai status, tetapi kodenya tidak lagi dapat dibaca dan tidak lebih pendek. Mungkin lebih lambat juga, karena saya mengeluarkan biaya 1 salinan dengan tupel.

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> result = f(x); // or "auto result = f(x)"
if(!result.get<2>()) {
    ... report error, error handling ...
}

Sisi negatif lainnya yang signifikan tersembunyi di sini- dengan "ReturnInts" saya dapat menambahkan kembali "f" dengan memodifikasi "ReturnInts" TANPA MENGUBAH INTERFACE "f". Solusi tupel tidak menawarkan fitur penting itu, yang menjadikannya jawaban inferior untuk kode library apa pun.


1
Pengecualian membuat antarmuka itu jauh lebih bersih.
David Stone

Agar adil (dan sangat terlambat ke pesta) Anda dapat memudahkan pembacaan dengan mengatur using std::tuple;dan hanya menggunakan tupledalam kode.
Alrekr

2
Menggunakan tuplemembuat kode kurang dapat dibaca, tidak lebih. Sebagian besar kode saat ini memiliki jumlah simbol yang sangat besar di dalamnya - melihat std::tupledengan jelas apa sebenarnya kode itu.
Tom Swirly

3

Tentu saja tuple dapat berguna, tetapi seperti yang telah disebutkan, ada sedikit overhead dan satu atau dua rintangan yang harus Anda lewati sebelum Anda benar-benar dapat menggunakannya.

Jika program Anda secara konsisten menemukan tempat di mana Anda perlu mengembalikan beberapa nilai atau menukar beberapa nilai, mungkin ada baiknya menggunakan rute tuple, tetapi sebaliknya terkadang lebih mudah untuk melakukan sesuatu dengan cara klasik.

Secara umum, tidak semua orang sudah menginstal Boost, dan saya pasti tidak akan repot-repot mengunduhnya dan mengonfigurasi direktori include saya untuk bekerja dengannya hanya untuk fasilitas tuple-nya. Saya pikir Anda akan menemukan bahwa orang-orang yang sudah menggunakan Boost lebih cenderung menemukan penggunaan tuple dalam program mereka daripada pengguna non-Boost, dan migran dari bahasa lain (yang muncul dalam pikiran Python) lebih cenderung kesal karena kurangnya tupel di C ++ daripada mempelajari metode menambahkan dukungan tupel.


1

Karena penyimpanan data std::tuplememiliki karakteristik terburuk dari a structdan larik; semua akses didasarkan pada posisi ke-n tetapi seseorang tidak dapat melakukan iterasi melalui tupleafor lingkaran.

Jadi jika elemen dalam tuplesecara konseptual adalah sebuah array, saya akan menggunakan sebuah array dan jika elemen tersebut bukan sebuah array secara konseptual, sebuah struct (yang memiliki nama elemen) lebih dapat dipelihara. ( a.lastnamelebih jelas daripadastd::get<1>(a) ).

Ini meninggalkan transformasi yang disebutkan oleh OP sebagai satu-satunya usecase yang layak untuk tupel.


0

Saya merasa banyak yang menggunakan Boost.Any dan Boost.Variant (dengan beberapa teknik) daripada Boost.Tuple.


Mengapa Anda menukar pengetikan statis yang efisien untuk sesuatu seperti itu?
Zifre

Boost.Variant sepenuhnya aman untuk tipe.
pengguna21714

1
Ups, ya itu aman untuk mengetik, tetapi ia melakukan pengetikan waktu proses.
Zifre

5
Saya tidak melihat bagaimana Tuple bisa diganti Any / Variant. Mereka tidak melakukan hal yang sama.
Mankarse

1
@Zifre Saya tidak dapat berbicara mewakili penulis, tetapi saya pikir implikasinya di sini adalah menggunakannya bersama dengan jenis wadah lainnya.
Tim Seguine
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.