Bagaimana cara menghapus salinan saat dirantai?

10

Saya membuat kelas tipe chaining, seperti contoh kecil di bawah ini. Tampaknya ketika fungsi chaining anggota, maka konstruktor salinan dipanggil. Apakah ada cara untuk menghilangkan panggilan copy constructor? Dalam contoh mainan saya di bawah ini, jelas bahwa saya hanya berurusan dengan hal-hal yang bersifat sementara dan dengan demikian "harus" (mungkin tidak menurut standar, tetapi secara logis) menjadi sebuah keputusan. Pilihan terbaik kedua, untuk menyalin elision, akan menjadi untuk memindahkan konstruktor dipanggil, tetapi ini tidak terjadi.

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

Sunting: Beberapa klarifikasi. 1. Ini tidak tergantung pada level optimisasi. 2. Dalam kode saya yang sebenarnya, saya memiliki objek yang lebih kompleks (misal tumpukan dialokasikan) daripada ints

DDaniel
sumber
Level optimasi apa yang Anda gunakan untuk kompilasi?
JVApen
2
auto b = test_class{7};tidak memanggil copy constructor karena itu benar-benar sama dengan test_class b{7};dan kompiler cukup pintar untuk mengenali kasus ini dan karena itu dapat dengan mudah menghilangkan salinan apa pun. Hal yang sama tidak dapat dilakukan untuk b2.
Beberapa programmer dude
Dalam contoh yang ditunjukkan, mungkin tidak ada perbedaan nyata antara memindahkan dan menyalin dan tidak semua orang menyadarinya. Jika Anda memasukkan sesuatu seperti vektor besar ke sana, mungkin itu masalah lain. Biasanya memindahkan hanya masuk akal untuk sumber daya menggunakan jenis (seperti menggunakan banyak tumpukan, dll.) - Apakah itu yang terjadi di sini?
darune
Contohnya terlihat dibuat-buat. Apakah Anda benar-benar memiliki I / O ( std::cout) di copy copy Anda? Tanpanya salinan harus dioptimalkan.
rustyx
@ rustyx, hapus std :: cout dan buat salinan konstruktor secara eksplisit. Ini menunjukkan bahwa salinan elision tidak bergantung pada std :: cout.
DDaniel

Jawaban:

7
  1. Sebagian jawaban (tidak membangun b2di tempat, tetapi mengubah konstruksi salinan menjadi konstruksi bergerak): Anda dapat membebani incrementfungsi anggota pada kategori nilai instance terkait:

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    Ini menyebabkan

    auto b2 = test_class{7}.increment();

    untuk bergerak-membangun b2karena test_class{7}bersifat sementara, dan &&kelebihantest_class::increment disebut.

  2. Untuk konstruksi di tempat yang sebenarnya (yaitu bahkan bukan konstruksi pemindahan), Anda dapat mengubah semua fungsi anggota khusus dan non-khusus menjadi constexprversi. Lalu, Anda bisa melakukannya

    constexpr auto b2 = test_class{7}.increment();

    dan Anda tidak bergerak atau menyalin konstruksi untuk membayar. Ini, jelas, mungkin untuk yang sederhana test_class, tetapi tidak untuk skenario yang lebih umum yang tidak memungkinkan untuk constexprfungsi anggota.

lubgr
sumber
Alternatif kedua juga membuat b2tidak dapat dimodifikasi.
Beberapa programmer dude
1
@Someprogrammerdude Poin bagus. Saya kira constinitakan menjadi cara untuk pergi ke sana.
lubgr
Apa tujuan dari ampersand setelah nama fungsi? Saya belum pernah melihat itu sebelumnya.
Philip Nelson
1
@ PhilipNelson mereka adalah ref-kualifikasi dan dapat menentukan apa thisyang sama dengan constfungsi anggota
kmdreko
1

Pada dasarnya, menetapkan ke suatu membutuhkan pemohon konstruktor, yaitu salinan atau perpindahan . Ini berbeda dari mana diketahui di kedua sisi fungsi sebagai objek berbeda yang sama. Juga dapat merujuk ke objek bersama seperti pointer.


Cara paling sederhana mungkin membuat konstruktor salinan sepenuhnya dioptimalkan. Pengaturan nilai sudah dioptimalkan oleh kompiler, hanya saja std::coutyang tidak dapat dioptimalkan.

test_class(const test_class& t) = default;

(atau cukup hapus salinan dan pindahkan konstruktor)

contoh hidup


Karena masalah Anda pada dasarnya dengan referensi, solusi mungkin tidak mengembalikan referensi ke objek jika Anda ingin berhenti menyalin dengan cara ini.

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

Metode ketiga hanya mengandalkan salin salinan di tempat pertama - namun ini membutuhkan penulisan ulang atau enkapsulasi operasi menjadi satu fungsi dan dengan demikian menghindari masalah ini sama sekali (saya sadar ini mungkin bukan yang Anda inginkan, tetapi bisa menjadi solusi untuk pengguna lain):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

Metode keempat menggunakan langkah sebagai gantinya, baik dipanggil eksplisit

auto b2 = std::move(test_class{7}.increment());

atau seperti yang terlihat dalam jawaban ini .

sayang
sumber
@ O'Neil memikirkan kasus lain - ty, dikoreksi
darune