Pertanyaan Kerugian menggunakan referensi const ketika iterasi atas tipe dasar?


Saya menemukan diri saya menggunakan C ++ 11 lebih dan lebih akhir-akhir ini, dan di mana saya akan menggunakan iterator di masa lalu, saya sekarang sedang menggunakan berbasis rentang untuk loop sedapat mungkin:

std::vector<int> coll(10);
std::generate(coll.begin(), coll.end(), []() { return rand(); } );

C ++ 03:

for (std::vector<int>::const_iterator it = coll.begin(); it != coll.end(); ++it) {
   foo_func(*it);
}

C ++ 11:

for (auto e : coll) { foo_func(e); }

Tetapi bagaimana jika jenis elemen koleksi adalah parameter template? foo_func() mungkin akan kelebihan beban untuk melewati jenis kompleks (= mahal untuk menyalin) berdasarkan referensi const, dan yang sederhana berdasarkan nilainya:

foo_func(const BigType& e) { ... };
foo_func(int e) { ... };

Saya tidak terlalu memikirkan ini ketika saya menggunakan kode C ++ 03-style di atas. Saya akan mengulangi dengan cara yang sama, dan karena dereferencing const_iterator menghasilkan referensi const, semuanya baik-baik saja. Tetapi menggunakan C ++ 11 range-based for loop, saya perlu menggunakan variabel loop referensi const untuk mendapatkan perilaku yang sama:

for (const auto& e : coll) { foo_func(e); }

Dan tiba-tiba saya tidak yakin lagi, jika ini tidak akan memperkenalkan instruksi perakitan yang tidak perlu auto adalah tipe sederhana (seperti pointer di belakang layar untuk mengimplementasikan referensi).

Tetapi kompilasi aplikasi sampel menegaskan bahwa tidak ada overhead untuk tipe sederhana, dan ini tampaknya menjadi cara umum untuk menggunakan rentang berbasis loop dalam template. Jika ini bukan kasusnya, boost :: call_traits :: param_type akan menjadi cara untuk pergi.

Pertanyaan: Apakah ada jaminan dalam standar?

(Saya menyadari bahwa masalah ini tidak benar-benar terkait dengan rentang berbasis loop. Ini juga ada saat menggunakan const_iterators.)


32
2017-10-24 20:55


asal


Jawaban:


Semua kontainer standar mengembalikan referensi dari iterator mereka (perhatikan, bagaimanapun, bahwa beberapa "kontainer tidak benar-benar kontainer, misalnya, std::vector<bool> yang mengembalikan proxy). Iterator lain mungkin mengembalikan proxy atau nilai meskipun ini tidak didukung secara ketat.

Tentu saja, standar tidak memberikan jaminan sehubungan dengan kinerja. Setiap jenis fitur terkait kinerja (di luar jaminan kompleksitas) dianggap sebagai kualitas implementasi.

Yang mengatakan, Anda mungkin ingin mempertimbangkan memiliki compiler membuat pilihan untuk Anda seperti yang dilakukan sebelumnya:

for (auto&& e: coll) { f(e); }

Masalah utama di sini adalah itu f() dapat menerimaconst referensi. Ini dapat dicegah jika perlu menggunakan const versi dari coll.


13
2017-10-24 21:13



6.5.4 / 1 mengatakan:

for ( for-range-declaration : braced-init-list ) statement

biarkan range-init setara dengan braced-init-list. Dalam setiap kasus, a   berdasarkan rentang untuk pernyataan setara dengan

{
    auto && __range = range-init;
    for ( auto __begin = begin-expr,
                __end = end-expr;
            __begin != __end;
            ++__begin ) {
        for-range-declaration = *__begin;
        statement
    }
}

(penjelasan lebih lanjut tentang makna semua itu __ gubbins).

Standar tidak menghasilkan apa-apa jaminan apakah garis itu const auto &e = *__begin memperkenalkan overhead kinerja, tentu saja, dibandingkan dengan menggunakan langsung *__begin dari pada e dalam pernyataan. Implementasi diizinkan untuk mengimplementasikan referensi dengan susah payah menyalin pointer ke beberapa stack slot dan kemudian membacanya kembali setiap kali referensi digunakan, dan tidak diperlukan untuk mengoptimalkan.

Tapi tidak ada alasan mengapa harus ada overhead di compiler yang masuk akal, dalam kasus di mana __beginadalah iterator kontainer (yang operator* mengembalikan referensi), dan kemudian e dilewatkan oleh nilai dalam pernyataan.


11
2017-10-24 21:16