Apa itu Aturan Tiga?


2150
  • Apa artinya menyalin objek ?
  • Apa konstruktor salin dan operator penugasan salinan ?
  • Kapan saya harus menyatakannya sendiri?
  • Bagaimana saya bisa mencegah objek saya disalin?

52
Silakan baca seluruh thread ini dan yang c++-faqtag wiki sebelum Anda memilih untuk menutup .
sbi

13
@ Biner: Setidaknya luangkan waktu untuk membaca diskusi komentar sebelum Anda memberikan suara. Teks itu dulunya lebih sederhana, tetapi Fred diminta untuk mengembangkannya. Selain itu, sementara itu empat pertanyaan secara tata bahasa , sebenarnya hanya satu pertanyaan dengan beberapa aspek. (Jika Anda tidak setuju dengan hal itu, buktikan POV Anda dengan menjawab masing-masing pertanyaan itu sendiri dan biarkan kami memberikan suara pada hasilnya.)
sbi

1
Fred, ini tambahan menarik untuk jawaban Anda mengenai C ++ 1x: stackoverflow.com/questions/4782757/… . Bagaimana kita menangani ini?
sbi


4
Perlu diingat bahwa, pada C ++ 11, saya pikir ini telah ditingkatkan ke aturan lima, atau sesuatu seperti itu.
paxdiablo

Jawaban:


1795

pengantar

C ++ memperlakukan variabel tipe yang ditentukan pengguna dengan semantik nilai . Ini berarti bahwa objek secara tersirat disalin dalam berbagai konteks, dan kita harus memahami apa sebenarnya arti "menyalin objek".

Mari kita perhatikan contoh sederhana:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(Jika Anda bingung dengan name(name), age(age)bagian ini, ini disebut daftar penginisialisasi anggota .)

Fungsi anggota khusus

Apa artinya menyalin personobjek? The mainFungsi menunjukkan dua skenario menyalin berbeda. Inisialisasi person b(a);dilakukan oleh copy constructor . Tugasnya adalah membangun objek baru berdasarkan keadaan objek yang ada. Penugasan b = adilakukan oleh operator penugasan salinan . Pekerjaannya umumnya sedikit lebih rumit, karena objek target sudah dalam keadaan valid yang perlu ditangani.

Karena kami mendeklarasikan bukan pembuat salinan atau operator penugasan (atau destruktor) sendiri, ini secara implisit didefinisikan untuk kami. Kutipan dari standar:

Operator penyalin dan penyalin salin [...], [...] dan destruktor adalah fungsi anggota khusus. [ Catatan : Implementasi secara implisit akan mendeklarasikan fungsi anggota ini untuk beberapa tipe kelas ketika program tidak secara eksplisit mendeklarasikannya. Implementasi akan secara implisit menentukan mereka jika digunakan. [...] catatan akhir ] [n3126.pdf bagian 12 §1]

Secara default, menyalin objek berarti menyalin anggotanya:

Konstruktor salinan yang terdefinisi secara implisit untuk kelas X non-serikat melakukan salinan anggota sub-proyeknya. [n3126.pdf bagian 12.8 §16]

Operator penugasan salinan yang ditentukan secara implisit untuk kelas X non-serikat pekerja melakukan penugasan salin anggota dari sub-proyeknya. [n3126.pdf bagian 12.8 §30]

Definisi tersirat

Fungsi anggota khusus yang didefinisikan secara implisit untuk personterlihat seperti ini:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

Penyalinan dengan anggota adalah yang kita inginkan dalam kasus ini: namedan agedisalin, jadi kita mendapatkan personobjek mandiri yang mandiri . Destructor yang didefinisikan secara implisit selalu kosong. Ini juga baik dalam hal ini karena kami tidak memperoleh sumber daya apa pun di konstruktor. Destructor anggota secara implisit dipanggil setelah persondestruktor selesai:

Setelah mengeksekusi tubuh destruktor dan menghancurkan objek otomatis yang dialokasikan dalam tubuh, destruktor untuk kelas X memanggil destruktor untuk anggota langsung [...] [n3126.pdf 12.4 §6]

Mengelola sumber daya

Jadi kapan kita harus mendeklarasikan fungsi anggota khusus itu secara eksplisit? Ketika kelas kita mengelola sumber daya , yaitu, ketika sebuah objek kelas bertanggung jawab atas sumber daya itu. Itu biasanya berarti sumber daya diperoleh dalam konstruktor (atau diteruskan ke konstruktor) dan dirilis pada destruktor.

Mari kita kembali ke masa pra-standar C ++. Tidak ada yang namanya std::string, dan programmer suka dengan pointer. The personkelas mungkin tampak seperti ini:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

Bahkan hari ini, orang masih menulis kelas dengan gaya ini dan mendapat masalah: " Saya mendorong seseorang ke vektor dan sekarang saya mendapatkan kesalahan memori gila! " Ingat bahwa secara default, menyalin objek berarti menyalin anggota, tetapi menyalin nameanggota hanya menyalin sebuah pointer, bukan array karakter yang ditunjuknya! Ini memiliki beberapa efek yang tidak menyenangkan:

  1. Perubahan melalui adapat diamati melalui b.
  2. Setelah bdihancurkan, a.nameadalah pointer menjuntai.
  3. Jika adihancurkan, menghapus pointer menggantung menghasilkan perilaku yang tidak terdefinisi .
  4. Karena tugas tidak memperhitungkan apa yang namemenunjuk sebelum tugas, cepat atau lambat Anda akan mendapatkan kebocoran memori di semua tempat.

Definisi eksplisit

Karena penyalinan dengan anggota tidak memiliki efek yang diinginkan, kita harus mendefinisikan konstruktor salin dan operator penugasan secara eksplisit untuk membuat salinan mendalam dari susunan karakter:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

Perhatikan perbedaan antara inisialisasi dan penugasan: kita harus merobohkan keadaan lama sebelum menugaskan nameuntuk mencegah kebocoran memori. Juga, kita harus melindungi diri dari penugasan formulir x = x. Tanpa centang itu, delete[] nameakan menghapus array yang berisi string sumber , karena ketika Anda menulis x = x, keduanya this->namedan that.nameberisi pointer yang sama.

Keamanan pengecualian

Sayangnya, solusi ini akan gagal jika new char[...]melempar pengecualian karena kehabisan memori. Salah satu solusi yang mungkin adalah dengan memperkenalkan variabel lokal dan menyusun ulang pernyataan:

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

Ini juga menangani penugasan diri tanpa pemeriksaan eksplisit. Solusi yang lebih kuat untuk masalah ini adalah idiom copy-and-swap , tapi saya tidak akan membahas detail keamanan pengecualian di sini. Saya hanya menyebutkan pengecualian untuk membuat poin berikut: Menulis kelas yang mengelola sumber daya sulit.

Sumber daya yang tidak dapat didokumentasikan

Beberapa sumber daya tidak dapat atau tidak boleh disalin, seperti pegangan file atau mutex. Dalam hal itu, cukup deklarasikan copy constructor dan copy assignment operator privatetanpa memberikan definisi:

private:

    person(const person& that);
    person& operator=(const person& that);

Atau, Anda dapat mewarisi boost::noncopyableatau mendeklarasikannya sebagai dihapus (dalam C ++ 11 dan di atas):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

Aturan tiga

Terkadang Anda perlu mengimplementasikan kelas yang mengelola sumber daya. (Jangan pernah mengelola banyak sumber daya dalam satu kelas, ini hanya akan menimbulkan rasa sakit.) Dalam hal ini, ingat aturan tiga :

Jika Anda perlu secara eksplisit mendeklarasikan baik destruktor, copy constructor atau operator penugasan sendiri, Anda mungkin perlu secara eksplisit mendeklarasikan ketiganya.

(Sayangnya, "aturan" ini tidak ditegakkan oleh standar C ++ atau kompiler apa pun yang saya ketahui.)

Aturan lima

Dari C ++ 11 dan seterusnya, sebuah objek memiliki 2 fungsi anggota khusus ekstra: konstruktor pemindahan dan pemindahan tugas. Aturan lima negara untuk mengimplementasikan fungsi-fungsi ini juga.

Contoh dengan tanda tangan:

class person
{
    std::string name;
    int age;

public:
    person(const std::string& name, int age);        // Ctor
    person(const person &) = default;                // Copy Ctor
    person(person &&) noexcept = default;            // Move Ctor
    person& operator=(const person &) = default;     // Copy Assignment
    person& operator=(person &&) noexcept = default; // Move Assignment
    ~person() noexcept = default;                    // Dtor
};

Aturan nol

Aturan 3/5 juga disebut sebagai aturan 0/3/5. Bagian nol dari aturan menyatakan bahwa Anda diizinkan untuk tidak menulis fungsi anggota khusus saat membuat kelas Anda.

Nasihat

Sebagian besar waktu, Anda tidak perlu mengelola sumber daya sendiri, karena kelas yang ada seperti std::stringsudah melakukannya untuk Anda. Bandingkan saja kode sederhana menggunakan std::stringanggota dengan alternatif berbelit-belit dan rawan menggunakan char*dan Anda harus diyakinkan. Selama Anda tinggal jauh dari anggota pointer mentah, aturan tiga kemungkinan tidak akan menyangkut kode Anda sendiri.


4
Fred, saya akan merasa lebih baik tentang suara saya jika (A) Anda tidak akan mengeja penugasan yang diterapkan dengan buruk dalam kode yang dapat disalin dan menambahkan catatan yang mengatakan itu salah dan mencari di tempat lain dalam fineprint; baik menggunakan c & s dalam kode atau melompati penerapan semua anggota ini (B) Anda akan mempersingkat babak pertama, yang tidak ada hubungannya dengan RoT; (C) Anda akan membahas pengantar semantik bergerak dan apa artinya itu bagi RoT.
sbi

7
Tapi kemudian pos harus dibuat C / W, saya pikir. Saya suka bahwa Anda menjaga istilah yang paling akurat (yaitu bahwa Anda mengatakan " operator penugasan copy ", dan bahwa Anda tidak memanfaatkan perangkap umum bahwa penugasan tidak dapat menyiratkan salinan).
Johannes Schaub - litb

4
@Prasoon: Saya tidak berpikir memotong setengah dari jawaban akan dilihat sebagai "penyuntingan yang adil" dari jawaban non-CW.
sbi

69
Akan lebih baik jika Anda memperbarui posting Anda untuk C ++ 11 (yaitu memindahkan konstruktor / tugas)
Alexander Malakhov

5
@solalito Apa pun yang harus Anda lepaskan setelah digunakan: kunci konkurensi, pegangan file, koneksi basis data, soket jaringan, memori tumpukan ...
fredoverflow

510

The Rule of Three adalah aturan praktis untuk C ++, pada dasarnya mengatakan

Jika kelas Anda membutuhkan

  • a copy constructor ,
  • sebuah operator penugasan ,
  • atau destruktor ,

didefinisikan secara eksplisit, maka kemungkinan membutuhkan mereka bertiga .

Alasan untuk ini adalah bahwa ketiganya biasanya digunakan untuk mengelola sumber daya, dan jika kelas Anda mengelola sumber daya, biasanya perlu mengelola penyalinan serta membebaskan.

Jika tidak ada semantik yang baik untuk menyalin sumber daya yang dikelola kelas Anda, maka pertimbangkan untuk melarang menyalin dengan mendeklarasikan (tidak mendefinisikan ) operator penyalin dan penugasan sebagai private.

(Perhatikan bahwa versi baru standar C ++ yang akan datang (yang merupakan C ++ 11) menambahkan pindahan semantik ke C ++, yang kemungkinan akan mengubah Aturan Tiga. Namun, saya tahu terlalu sedikit tentang ini untuk menulis bagian C ++ 11 tentang Aturan Tiga.)


3
Solusi lain untuk mencegah penyalinan adalah mewarisi (secara pribadi) dari kelas yang tidak dapat disalin (seperti boost::noncopyable). Itu juga bisa menjadi lebih jelas. Saya berpikir bahwa C ++ 0x dan kemungkinan untuk "menghapus" fungsi dapat membantu di sini, tetapi lupa sintaksnya: /
Matthieu M.

2
@ Matthieu: Yap, itu juga berfungsi. Tetapi kecuali jika itu noncopyableadalah bagian dari std lib, saya tidak menganggapnya sebagai perbaikan. (Oh, dan jika Anda lupa sintaks penghapusan, Anda lupa mor ethan yang pernah saya kenal. :))
sbi

3
@ Kan: Lihat jawaban ini . Namun, saya akan merekomendasikan untuk tetap Martinho 's Rule of Nol . Bagi saya, ini adalah salah satu aturan praktis yang paling penting untuk C ++ yang dibuat dalam dekade terakhir.
sbi

3
Aturan Nol Martinho sekarang lebih baik (tanpa pengambilalihan adware yang jelas) terletak di archive.org
Nathan Kidd

161

Hukum tiga besar adalah sebagaimana ditentukan di atas.

Contoh mudah, dalam bahasa Inggris sederhana, dari jenis masalah yang dipecahkannya:

Destruktor non default

Anda mengalokasikan memori di konstruktor Anda dan karenanya Anda perlu menulis destruktor untuk menghapusnya. Kalau tidak, Anda akan menyebabkan kebocoran memori.

Anda mungkin berpikir bahwa ini adalah pekerjaan yang dilakukan.

Masalahnya adalah, jika salinan dibuat dari objek Anda, maka salinan itu akan menunjuk ke memori yang sama dengan objek aslinya.

Sekali, salah satu dari ini menghapus memori dalam destruktornya, yang lain akan memiliki pointer ke memori yang tidak valid (ini disebut pointer menggantung) ketika mencoba menggunakannya hal-hal yang akan menjadi berbulu.

Oleh karena itu, Anda menulis copy constructor sehingga mengalokasikan objek baru yang akan dihancurkan oleh memori mereka sendiri.

Operator penugasan dan copy constructor

Anda mengalokasikan memori di konstruktor Anda ke pointer anggota kelas Anda. Saat Anda menyalin objek kelas ini, operator penugasan default dan copy constructor akan menyalin nilai dari pointer anggota ini ke objek baru.

Ini berarti bahwa objek baru dan objek lama akan menunjuk pada bagian memori yang sama sehingga ketika Anda mengubahnya di satu objek itu akan berubah untuk objek lain juga. Jika satu objek menghapus memori ini, yang lain akan melanjutkan mencoba menggunakannya - eek.

Untuk mengatasinya, Anda menulis versi konstruktor dan tugas penugasan versi Anda sendiri. Versi Anda mengalokasikan memori terpisah ke objek-objek baru dan menyalin nilai-nilai yang ditunjuk oleh pointer pertama daripada alamatnya.


4
Jadi, jika kita menggunakan copy constructor maka copy dibuat tetapi pada lokasi memori yang berbeda sama sekali dan jika kita tidak menggunakan copy constructor maka copy dibuat tetapi menunjuk ke lokasi memori yang sama. Apakah itu yang ingin Anda katakan? Jadi salinan tanpa copy constructor berarti bahwa pointer baru akan ada di sana tetapi menunjuk ke lokasi memori yang sama namun jika kita memiliki copy constructor yang secara eksplisit ditentukan oleh pengguna maka kita akan memiliki pointer terpisah yang menunjuk ke lokasi memori yang berbeda tetapi memiliki data.
Unbreakable

4
Maaf, saya membalas ini berabad-abad yang lalu tetapi jawaban saya tampaknya tidak tetap ada di sini :-( Pada dasarnya, ya - Anda mendapatkannya :-)
Stefan

1
Bagaimana prinsip berlaku untuk operator penugasan salinan? Jawaban ini akan lebih bermanfaat jika yang ketiga dalam Aturan Tiga disebutkan.
DBedrenko

1
@ DBedrenko, "Anda menulis copy constructor sehingga ia mengalokasikan objek baru pada memori mereka sendiri ..." ini adalah prinsip yang sama yang meluas ke operator penugasan copy. Tidakkah kamu pikir aku sudah menjelaskannya?
Stefan

2
@ DBedrenko, saya telah menambahkan beberapa informasi lebih lanjut. Apakah itu membuatnya lebih jelas?
Stefan

44

Pada dasarnya jika Anda memiliki destruktor (bukan destruktor default) itu berarti kelas yang Anda tentukan memiliki alokasi memori. Misalkan kelas digunakan di luar oleh beberapa kode klien atau oleh Anda.

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

Jika MyClass hanya memiliki beberapa anggota yang diketik primitif operator penugasan default akan bekerja tetapi jika memiliki beberapa anggota penunjuk dan objek yang tidak memiliki operator penugasan, hasilnya akan tidak dapat diprediksi. Oleh karena itu kita dapat mengatakan bahwa jika ada sesuatu untuk dihapus pada destruktor suatu kelas, kita mungkin memerlukan operator penyalinan yang dalam yang berarti kita harus menyediakan operator penyalin dan penugasan salinan.


36

Apa artinya menyalin objek? Ada beberapa cara Anda dapat menyalin objek - mari kita bicara tentang 2 jenis yang paling Anda rujuk - salinan dalam dan salinan dangkal.

Karena kita menggunakan bahasa berorientasi objek (atau setidaknya mengasumsikan demikian), katakanlah Anda memiliki memori yang dialokasikan. Karena ini adalah bahasa OO, kita dapat dengan mudah merujuk pada potongan memori yang kita alokasikan karena mereka biasanya variabel primitif (ints, karakter, byte) atau kelas yang kita definisikan terbuat dari tipe dan primitif kita sendiri. Jadi katakanlah kita memiliki kelas Mobil sebagai berikut:

class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;

public changePaint(String newColor)
{
   this.sPrintColor = newColor;
}

public Car(String model, String make, String color) //Constructor
{
   this.sPrintColor = color;
   this.sModel = model;
   this.sMake = make;
}

public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}

public Car(const Car &other) // Copy Constructor
{
   this.sPrintColor = other.sPrintColor;
   this.sModel = other.sModel;
   this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
   if(this != &other)
   {
      this.sPrintColor = other.sPrintColor;
      this.sModel = other.sModel;
      this.sMake = other.sMake;
   }
   return *this;
}

}

Salinan yang dalam adalah jika kita mendeklarasikan objek dan kemudian membuat salinan objek yang sepenuhnya terpisah ... kita berakhir dengan 2 objek dalam 2 set memori yang sepenuhnya.

Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.

Sekarang mari kita lakukan sesuatu yang aneh. Katakanlah car2 diprogram dengan salah atau dengan sengaja dimaksudkan untuk membagikan memori aktual yang dibuat dari car1. (Biasanya kesalahan untuk melakukan ini dan di kelas biasanya selimut itu dibahas di bawah.) Berpura-pura bahwa setiap kali Anda bertanya tentang car2, Anda benar-benar menyelesaikan pointer ke ruang memori car1 ... itu kurang lebih seperti salinan yang dangkal adalah.

//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

 Car car1 = new Car("ford", "mustang", "red"); 
 Car car2 = car1; 
 car2.changePaint("green");//car1 is also now green 
 delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve 
 the address of where car2 exists and delete the memory...which is also
 the memory associated with your car.*/
 car1.changePaint("red");/*program will likely crash because this area is
 no longer allocated to the program.*/

Jadi, terlepas dari bahasa apa yang Anda tulis, berhati-hatilah dengan apa yang Anda maksud ketika menyalin objek karena sebagian besar waktu Anda menginginkan salinan yang dalam.

Apa konstruktor salin dan operator penugasan salinan? Saya sudah menggunakannya di atas. Copy constructor dipanggil ketika Anda mengetik kode seperti pada Car car2 = car1; dasarnya jika Anda mendeklarasikan variabel dan menetapkannya dalam satu baris, saat itulah copy constructor dipanggil. Operator penugasan adalah apa yang terjadi ketika Anda menggunakan tanda sama dengan-- car2 = car1;. Pemberitahuan car2tidak dinyatakan dalam pernyataan yang sama. Dua potongan kode yang Anda tulis untuk operasi ini kemungkinan sangat mirip. Sebenarnya pola desain yang khas memiliki fungsi lain yang Anda panggil untuk mengatur semuanya setelah Anda puas dengan salinan / penugasan awal yang sah - jika Anda melihat kode lama yang saya tulis, fungsinya hampir identik.

Kapan saya harus menyatakannya sendiri? Jika Anda tidak menulis kode yang akan dibagikan atau untuk produksi dengan cara tertentu, Anda benar-benar hanya perlu menyatakannya saat Anda membutuhkannya. Anda harus mengetahui apa yang dilakukan bahasa program Anda jika Anda memilih untuk menggunakannya 'secara tidak sengaja' dan tidak membuatnya - yaitu Anda mendapatkan default kompiler. Saya jarang menggunakan copy constructor misalnya, tetapi menimpa operator penugasan sangat umum. Tahukah Anda bahwa Anda dapat mengesampingkan apa arti penambahan, pengurangan, dll. Juga?

Bagaimana saya bisa mencegah objek saya disalin? Mengganti semua cara Anda diizinkan mengalokasikan memori untuk objek Anda dengan fungsi pribadi adalah awal yang masuk akal. Jika Anda benar-benar tidak ingin orang menyalinnya, Anda dapat membuatnya publik dan mengingatkan programmer dengan melemparkan pengecualian dan juga tidak menyalin objek.


5
Pertanyaan itu ditandai C ++. Eksposisi pseudo-code ini tidak banyak mengklarifikasi apa pun tentang "Aturan Tiga" yang terdefinisi dengan baik, dan hanya menyebarkan kebingungan paling buruk.
lihat

26

Kapan saya harus menyatakannya sendiri?

Aturan Tiga menyatakan bahwa jika Anda mendeklarasikan salah satu dari a

  1. salin konstruktor
  2. menyalin operator penugasan
  3. destruktor

maka Anda harus mendeklarasikan ketiganya. Tumbuhnya dari pengamatan bahwa kebutuhan untuk mengambil alih arti dari operasi penyalinan hampir selalu berasal dari kelas yang melakukan semacam manajemen sumber daya, dan yang hampir selalu menyiratkan bahwa

  • manajemen sumber daya apa pun yang sedang dilakukan dalam satu operasi penyalinan mungkin perlu dilakukan dalam operasi penyalinan lainnya dan

  • destruktor kelas juga akan berpartisipasi dalam pengelolaan sumber daya (biasanya melepaskannya). Sumber daya klasik yang akan dikelola adalah memori, dan inilah sebabnya semua kelas Perpustakaan Standar yang mengelola memori (misalnya, wadah STL yang melakukan manajemen memori dinamis) semuanya menyatakan "tiga besar": operasi penyalinan dan destruktor.

Konsekuensi dari Aturan Tiga adalah bahwa keberadaan destruktor yang dideklarasikan oleh pengguna menunjukkan bahwa salinan bijaksana anggota sederhana tidak mungkin sesuai untuk operasi penyalinan di kelas. Itu, pada gilirannya, menunjukkan bahwa jika suatu kelas menyatakan destruktor, operasi penyalinan mungkin tidak boleh dibuat secara otomatis, karena mereka tidak akan melakukan hal yang benar. Pada saat C ++ 98 diadopsi, signifikansi dari garis penalaran ini tidak sepenuhnya dihargai, sehingga dalam C ++ 98, keberadaan pengguna yang dinyatakan destruktor tidak berdampak pada kesediaan kompiler untuk menghasilkan operasi penyalinan. Itu terus menjadi kasus di C ++ 11, tetapi hanya karena membatasi kondisi di mana operasi penyalinan dihasilkan akan memecah terlalu banyak kode warisan.

Bagaimana saya bisa mencegah objek saya disalin?

Menyatakan operator konstruktor & salin penyalinan sebagai penentu akses pribadi.

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

Di C ++ 11 dan seterusnya, Anda juga dapat mendeklarasikan copy constructor & operator penugasan dihapus

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}


10

Aturan tiga dalam C ++ adalah prinsip dasar desain dan pengembangan tiga persyaratan bahwa jika ada definisi yang jelas dalam salah satu fungsi anggota berikut, maka programmer harus mendefinisikan dua fungsi anggota lainnya secara bersamaan. Yaitu, tiga fungsi anggota berikut sangat diperlukan: destruktor, copy constructor, operator penugasan salinan.

Salin konstruktor di C ++ adalah konstruktor khusus. Ini digunakan untuk membangun objek baru, yang merupakan objek baru yang setara dengan salinan objek yang sudah ada.

Operator penugasan salin adalah operator penugasan khusus yang biasanya digunakan untuk menentukan objek yang ada untuk orang lain dari jenis objek yang sama.

Ada beberapa contoh cepat:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;

7
Hai, jawaban Anda tidak menambahkan sesuatu yang baru. Yang lain membahas subjek dengan lebih mendalam, dan lebih akurat - jawaban Anda adalah perkiraan dan bahkan salah di beberapa tempat (yaitu tidak ada "harus" di sini; itu "sangat mungkin harus"). Ini benar-benar tidak bernilai saat Anda memposting jawaban semacam ini untuk pertanyaan yang sudah dijawab secara menyeluruh. Kecuali Anda memiliki hal-hal baru untuk ditambahkan.
Mat

1
Juga, ada empat contoh cepat, yang entah bagaimana terkait dengan dua dari tiga yang dibicarakan oleh Aturan Tiga. Terlalu banyak kebingungan.
anatolyg
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.