Pytanie Co to jest zasada trzech?


  • Co robi kopiowanie obiektu oznaczać?
  • Jakie są skopiować konstruktora i operator przypisania kopii?
  • Kiedy muszę je zadeklarować?
  • Jak mogę zapobiec kopiowaniu moich obiektów?

1844
2017-11-13 13:27


pochodzenie


Proszę czytać ten cały wątek i c++-faq tag wiki zanim zagłosujesz, by zamknąć. - sbi
@Binary: Przynajmniej poświęć czas na przeczytanie dyskusji na komentarz przed oddałeś głos. Tekst był znacznie prostszy, ale Fred został poproszony o jego rozszerzenie. Ponadto, chociaż to są cztery pytania gramatycznie, to naprawdę tylko jedno pytanie z kilkoma aspektami. (Jeśli się z tym nie zgadzasz, udowodnij swój POV, odpowiadając na każde z tych pytań samodzielnie i pozwól nam głosować na wyniki). - sbi
Fred, oto interesujące uzupełnienie twojej odpowiedzi dotyczącej C ++ 1x: stackoverflow.com/questions/4782757/.... Jak sobie z tym poradzić? - sbi
Związane z: Prawo wielkiej dwójki - Nemanja Trifunovic
Należy pamiętać, że od C ++ 11, myślę, że zostało to ulepszone do zasady pięciu lub coś podobnego. - paxdiablo


Odpowiedzi:


Wprowadzenie

C ++ traktuje zmienne typów zdefiniowanych przez użytkownika za pomocą semantyka wartości. Oznacza to, że obiekty są niejawnie kopiowane w różnych kontekstach, i powinniśmy zrozumieć, co właściwie oznacza "kopiowanie obiektu".

Rozważmy prosty przykład:

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?
}

(Jeśli jesteś zdziwiony przez name(name), age(age) część, to się nazywa lista inicjalizatorów członków.)

Specjalne funkcje członków

Co to znaczy kopiować person obiekt? The main Funkcja pokazuje dwa różne scenariusze kopiowania. Inicjalizacja person b(a); jest wykonywane przez skopiować konstruktora. Jego zadaniem jest skonstruowanie nowego obiektu na podstawie stanu istniejącego obiektu. Przydzial b = a jest wykonywane przez operator przypisania kopii. Jego praca jest ogólnie nieco bardziej skomplikowana, ponieważ obiekt docelowy jest już w jakimś prawidłowym stanie, którym należy się zająć.

Ponieważ nie zadeklarowaliśmy siebie ani konstruktora kopii, ani operatora przypisania (ani destruktora), są one domyślnie zdefiniowane dla nas. Cytuj ze standardu:

Konstruktor kopi [...] i operator przypisania kopi, [...] i destruktor są specjalnymi funkcjami składowymi.   [ Uwaga: Implementacja będzie domyślnie deklarować te funkcje członkowskie   w przypadku niektórych typów klas, gdy program nie deklaruje ich jawnie.   Implementacja domyślnie je zdefiniuje, jeśli zostaną użyte. [...] nota końcowa ]   [n3126.pdf sekcja 12 §1]

Domyślnie kopiowanie obiektu oznacza kopiowanie jego członków:

Niejawnie zdefiniowany konstruktor kopii dla niepowiązanej klasy X wykonuje kopię swoich podobiektów.   [n3126.pdf sekcja 12.8 §16]

Niejawnie zdefiniowany operator przypisywania kopii dla nie zrzeszonej klasy X wykonuje przynależne kopiowanie   jego podobiektów.   [n3126.pdf sekcja 12.8 §30]

Niejawne definicje

Niejawnie zdefiniowane specjalne funkcje składowe dla person wygląda jak to:

// 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()
{
}

Dokładne kopiowanie jest dokładnie tym, czego chcemy w tym przypadku: name i age są kopiowane, więc otrzymujemy niezależne, niezależne person obiekt. Niejawnie zdefiniowany destruktor jest zawsze pusty. W tym przypadku jest to również w porządku, ponieważ nie uzyskaliśmy żadnych zasobów w konstruktorze. Destruktory członków są domyślnie wywoływane po person destruktor jest zakończony:

Po wykonaniu ciała destruktora i zniszczeniu wszystkich automatycznych obiektów przydzielonych w ciele,   destruktor klasy X wywołuje destruktory dla bezpośrednich [...] członków X.   [n3126.pdf 12.4 §6]

Zarządzanie zasobami

Kiedy powinniśmy wyraźnie zadeklarować te specjalne funkcje członkowskie? Kiedy nasza klasa zarządza zasobem, to jest, gdy przedmiotem jest klasa odpowiedzialny dla tego zasobu. To zwykle oznacza, że ​​zasób jest nabyty w konstruktorze (lub przeszedł do konstruktora) i wydany w destruktorze.

Wróćmy w czasie do standardowego C ++. Nie było czegoś takiego jak std::stringi programiści byli zakochani w wskaźnikach. The person klasa mogła wyglądać tak:

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;
    }
};

Nawet dzisiaj ludzie wciąż piszą zajęcia w tym stylu i wpadają w kłopoty: "Wepchnąłem osobę do wektora i teraz dostaję szalone błędy pamięci!" Pamiętaj, że domyślnie kopiowanie obiektu oznacza kopiowanie jego członków, ale kopiowanie name członek jedynie kopiuje wskaźnik, nie tablica znaków, którą wskazuje! Ma to kilka nieprzyjemnych skutków:

  1. Zmiany przez a można obserwować przez b.
  2. Pewnego razu b jest zniszczony, a.name jest zwisającym wskaźnikiem.
  3. Gdyby a jest zniszczony, usuwając zwisające wskaźniki wydajności niezdefiniowane zachowanie.
  4. Ponieważ cesja nie bierze pod uwagę tego, co name wskazał przed zleceniem, prędzej czy później dostaniesz wycieki pamięci w każdym miejscu.

Jawne definicje

Ponieważ kopiowanie członkowskie nie daje pożądanego efektu, musimy jawnie zdefiniować konstruktora kopiowania i operatora przypisania kopii, aby wykonać głębokie kopie tablicy znaków:

// 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;
}

Zauważ różnicę między inicjalizacją a przypisaniem: musimy zniszczyć stary stan przed przypisaniem do name aby zapobiec wyciekom pamięci. Musimy również zabezpieczyć się przed samozasłonięciem formularza x = x. Bez tego czeku, delete[] name usunie tablicę zawierającą źródło strunowy, ponieważ kiedy piszesz x = x, obie this->name i that.name zawierają ten sam wskaźnik.

Bezpieczeństwo wyjątków

Niestety, to rozwiązanie się nie powiedzie, jeśli new char[...] zgłasza wyjątek z powodu wyczerpania pamięci. Jednym z możliwych rozwiązań jest wprowadzenie zmiennej lokalnej i zmiana kolejności instrukcji:

// 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;
}

To również zajmuje się samodzielnym przypisaniem bez wyraźnej kontroli. Jeszcze bardziej niezawodnym rozwiązaniem tego problemu jest idiom kopiowania i zamiany, ale nie będę tutaj wchodził w szczegóły dotyczące bezpieczeństwa wyjątków. Wymieniłem tylko wyjątki, aby przedstawić następujący punkt: Pisanie klas zarządzających zasobami jest trudne.

Zasoby nieodferowane

Niektóre zasoby nie mogą lub nie powinny być kopiowane, takie jak uchwyty plików lub muteksy. W takim przypadku po prostu zadeklaruj konstruktor kopiowania i operator przypisania kopiowania jako private bez podania definicji:

private:

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

Możesz też dziedziczyć z boost::noncopyable lub zadeklaruj je jako usunięte (C ++ 0x):

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

Zasada trzech

Czasami trzeba zaimplementować klasę, która zarządza zasobem. (Nigdy nie zarządzaj wieloma zasobami w jednej klasie, to tylko doprowadzi do bólu.) W takim przypadku pamiętaj o zasada trzech:

Jeśli chcesz jawnie zadeklarować destruktor,   samodzielnie skopiować konstruktora lub operatora przypisania kopii,   prawdopodobnie musisz jawnie zadeklarować wszystkie trzy z nich.

(Niestety, ta "reguła" nie jest wymuszana przez standard C ++ lub jakikolwiek inny znany mi kompilator).

Rada

Przez większość czasu nie musisz samodzielnie zarządzać zasobami, ponieważ istniejąca klasa taka jak std::string już to robi dla ciebie. Wystarczy porównać prosty kod za pomocą a std::string członek do zawiłej i podatnej na błędy alternatywy za pomocą char* i powinieneś być przekonany. Dopóki trzymasz się z dala od surowych członków wskaźnika, reguła trzech raczej nie będzie dotyczyła twojego własnego kodu.


1517
2017-11-13 13:27



Fred, czułbym się lepiej z powodu mojego głosu uprzywilejowanego, gdyby (A) nie opisał źle zaimplementowanego zadania w kodzie nadającym się do kopiowania i dodał notatkę mówiącą, że jest źle i poszukaj w innym miejscu w projekcie; albo użyj c & s w kodzie, albo po prostu pomiń implementację wszystkich tych elementów (B) skracasz pierwszą połowę, co ma niewiele wspólnego z RoT; (C) omówilibyście wprowadzenie semantyki ruchu i co to oznacza dla RoT. - sbi
Ale wtedy post powinien być zrobiony C / W, myślę. Podoba mi się, że terminy są w większości dokładne (tzn.Kopiuj operator przypisania "i że nie wykorzystujesz wspólnej pułapki, której przypisanie nie może implikować kopii). - Johannes Schaub - litb
@Prasoon: Nie sądzę, że wycięcie połowy odpowiedzi byłoby postrzegane jako "uczciwa edycja" odpowiedzi innej niż CW. - sbi
Mogłem też go przejąć, ale nie wspomniałeś, że operator kopiowania powinien sprawdzić tożsamość przed wykonaniem jakiejkolwiek czynności. - Björn Pollex
Byłoby wspaniale, gdyby zaktualizować swój post dla C ++ 11 (to znaczy przenieść konstruktora / zadania) - Alexander Malakhov


The Zasada Trzech jest regułą dla C ++, mówiąc w zasadzie

Jeśli twoja klasa potrzebuje któregoś z nich

  • za skopiować konstruktora,
  • na operator przypisania,
  • lub burzyciel,

zdefiniowane explicitly, to prawdopodobnie będzie potrzebować wszystkie trzy.

Powodem tego jest to, że wszystkie trzy z nich są zazwyczaj używane do zarządzania zasobami, a jeśli klasa zarządza zasobem, zwykle musi zarządzać kopiowaniem i uwalnianiem.

Jeśli nie ma dobrej semantycznej metody kopiowania zasobu, którym zarządza twoja klasa, rozważ zabranie kopiowania poprzez deklarowanie (nie definiowanie) konstruktor kopii i operator przypisania jako private.

(Zauważ, że nadchodząca nowa wersja standardu C ++ (która jest C ++ 11) dodaje semantykę ruchu do C ++, co prawdopodobnie zmieni Regułę 3. Jednak za mało wiem o tym, aby napisać sekcję C ++ 11 o Regule Trzech).


451
2017-11-13 14:22



Innym rozwiązaniem zapobiegającym kopiowaniu jest dziedziczenie (prywatnie) z klasy, której nie można skopiować (np boost::noncopyable). Może być również dużo bardziej przejrzysty. Myślę, że C ++ 0x i możliwość "delete" funkcji może pomóc tutaj, ale zapomniałem składni: / - Matthieu M.
@Matthieu: Tak, to też działa. Ale chyba noncopyable jest częścią std lib, nie uważam tego za znaczną poprawę. (Aha, i jeśli zapomnieliście skasować składnię, zapomnieliście o moranie, jakiego kiedykolwiek znałem. :)) - sbi
Wszelkie aktualizacje dotyczące reguły trzech i C ++ 1x? - Daan Timmer
@Dan: Zobacz ta odpowiedź. Jednak polecam trzymać się Martinho„s Zasada zero. Dla mnie jest to jedna z najważniejszych zasad dla C ++ ukutych w ostatniej dekadzie. - sbi
Zasada zerowa Martinho znajduje się teraz tutaj - Diego


Prawo wielkiej trójki jest określone powyżej.

Prosty przykład, w prostym języku angielskim, tego rodzaju problemu, który rozwiązuje:

Domyślny destruktor

Podzieliłeś pamięć w swoim konstruktorze i musisz napisać destruktor, aby go usunąć. W przeciwnym razie spowoduje to wyciek pamięci.

Możesz pomyśleć, że to robota.

Problem będzie polegał na tym, że jeśli twoja kopia zostanie wykonana z twojego obiektu, kopia wskaże taką samą pamięć jak oryginalny obiekt.

Raz, jeden z nich usuwa pamięć w swoim destruktorze, drugi będzie miał wskaźnik do nieprawidłowej pamięci (to się nazywa zwisającym wskaźnikiem), gdy spróbuje go użyć, rzeczy będą się włochate.

Dlatego piszesz konstruktor kopiowania, aby przydzielał nowe obiekty do zniszczenia własnych kawałków pamięci.

Operator przypisania i konstruktor kopii

Podzieliłeś pamięć w swoim konstruktorze na wskaźnik klasy twojej klasy. Podczas kopiowania obiektu tej klasy, domyślny operator przypisania i konstruktor kopiowania skopiuje wartość tego wskaźnika członkowskiego do nowego obiektu.

Oznacza to, że nowy obiekt i stary obiekt będą wskazywać na tę samą część pamięci, więc jeśli zmienisz ją w jeden obiekt, zostanie ona zmieniona również dla innego obiektu. Jeśli jeden obiekt usunie tę pamięć, drugi będzie próbował go użyć - eek.

Aby rozwiązać ten problem, napisz własną wersję konstruktora kopiowania i operatora przypisania. Twoje wersje przydzielają oddzielną pamięć nowym obiektom i kopiują wartości wskazane przez pierwszy wskaźnik, a nie jego adres.


134
2018-05-14 14:22



Jeśli więc użyjemy konstruktora kopiowania, to kopia zostanie wykonana, ale w innym miejscu pamięci, a jeśli nie użyjemy konstruktora kopiowania, to zostanie wykonana kopia, ale wskazuje ona tę samą lokalizację pamięci. czy to właśnie próbujesz powiedzieć? Tak więc kopia bez konstruktora kopiowania oznacza, że ​​pojawi się nowy wskaźnik, ale wskazuje na to samo miejsce w pamięci, jednak jeśli mamy konstruktor kopiowania jawnie zdefiniowany przez użytkownika, będziemy mieli oddzielny wskaźnik wskazujący inną lokalizację pamięci, ale posiadającą dane. - Unbreakable
Przepraszam, odpowiedziałem na to wieki temu, ale moja odpowiedź nie wydaje się jeszcze tu być :-( Zasadniczo tak - rozumiesz :-) - Stefan
W jaki sposób zasada zależy od operatora przypisania kopii? Ta odpowiedź byłaby bardziej przydatna, gdyby wspomnieć o 3. w Regule Trzech. - DBedrenko
@DBedrenko, "piszesz konstruktor kopii, który przydziela nowe obiekty, własne fragmenty pamięci ..." to ta sama zasada, która rozciąga się na operatora przypisania kopiowania. Czy nie sądzisz, że jasno to wyjaśniłem? - Stefan
@Stefan Tak, wielkie dzięki! - DBedrenko


Zasadniczo, jeśli masz destruktor (nie jest to domyślny destruktor), oznacza to, że zdefiniowana klasa ma przydzieloną pamięć. Załóżmy, że klasa jest używana na zewnątrz przez jakiś kod klienta lub przez ciebie.

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

Jeśli MyClass ma tylko niektóre prymitywne wpisane elementy, domyślny operator przypisania działałby, ale jeśli ma kilka elementów wskaźnika i obiektów, które nie mają operatorów przypisania, rezultat byłby nieprzewidywalny. Dlatego możemy powiedzieć, że jeśli istnieje coś do usunięcia w destruktorze klasy, możemy potrzebować operatora głębokiego kopiowania, co oznacza, że ​​powinniśmy dostarczyć konstruktora kopiowania i operatora przypisania.


37
2017-12-31 19:29





Co oznacza kopiowanie obiektu? Istnieje kilka sposobów na kopiowanie obiektów - porozmawiajmy o dwóch rodzajach, do których najprawdopodobniej się odwołujesz - głębokiej kopii i płytkiej kopii.

Ponieważ jesteśmy w języku obiektowym (lub przynajmniej zakładamy), powiedzmy, że masz przydzieloną pamięć. Ponieważ jest to język OO, możemy łatwo odnieść się do fragmentów pamięci, które przydzielamy, ponieważ są to zwykle prymitywne zmienne (int, char, bajty) lub zdefiniowane przez nas klasy, które są wykonane z naszych własnych typów i prymitywów. Załóżmy, że mamy klasę samochodów w następujący sposób:

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;
}

}

Głęboka kopia jest wtedy, gdy deklarujemy obiekt, a następnie tworzymy całkowicie oddzielną kopię obiektu ... kończymy z 2 obiektami w 2 kompletnych zestawach pamięci.

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.

Teraz zróbmy coś dziwnego. Powiedzmy, że car2 jest zaprogramowany źle lub celowo ma na celu udostępnienie rzeczywistej pamięci, z której składa się car1. (Zazwyczaj jest to pomyłka, a na zajęciach jest zwykle kocyk, o którym jest mowa.) Udawaj, że kiedy tylko pytasz o samochód2, naprawdę rozwiązujesz wskaźnik do przestrzeni pamięci car1 ... to mniej więcej to, co płytka kopia jest.

//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.*/

Niezależnie od tego, w jakim języku piszesz, zachowaj ostrożność, jeśli chodzi o kopiowanie obiektów, ponieważ w większości przypadków potrzebujesz głębokiej kopii.

Co to jest konstruktor kopii i operator przypisania kopiowania? Użyłem ich już powyżej. Konstruktor kopiowania jest wywoływany podczas wpisywania kodu takiego jak Car car2 = car1;  Zasadniczo, jeśli zadeklarujesz zmienną i przypiszesz ją w jednym wierszu, wtedy wywoływany jest konstruktor kopiowania. Operator przypisania jest tym, co się dzieje, gdy używasz znaku równości--car2 = car1;. Ogłoszenie car2 nie jest zadeklarowane w tym samym wyciągu. Dwie części kodu, które piszesz dla tych operacji, są prawdopodobnie bardzo podobne. W rzeczywistości typowy wzorzec projektu ma inną funkcję, którą można wywołać, gdy wszystko jest w porządku, gdy początkowa kopia / zadanie jest zgodne z prawem - jeśli spojrzeć na kod, który napisałem, funkcje są prawie identyczne.

Kiedy muszę je zadeklarować? Jeśli nie piszesz kodu, który ma być udostępniony lub do produkcji w jakiś sposób, naprawdę wystarczy zadeklarować je, gdy ich potrzebujesz. Musisz mieć świadomość tego, co robi twój język programu, jeśli zdecydujesz się go użyć "przez przypadek" i go nie zrobił - tj. otrzymujesz domyślny kompilator. Rzadko używam na przykład konstruktorów kopiowania, ale nadpisanie operatora jest bardzo powszechne. Czy wiesz, że możesz również przesłonić znaczenie dodatku, odejmowania itp.?

Jak mogę zapobiec kopiowaniu moich obiektów? Zastąp wszystkie sposoby przydzielania pamięci dla obiektu za pomocą funkcji prywatnej, to rozsądny początek. Jeśli naprawdę nie chcesz, aby ludzie je kopiowali, możesz upublicznić i ostrzec programistę, rzucając wyjątek, a także nie kopiując obiektu.


27
2017-10-17 16:37



Pytanie zostało oznaczone jako C ++. Ta pseudokodowa ekspozycja niewiele może wyjaśnić, jeśli chodzi o dobrze zdefiniowaną "Regułę Trójki" w najlepszym przypadku, i po prostu w najgorszym przypadku rozprasza zamęt. - sehe


Kiedy muszę je zadeklarować?

Reguła Trzech stwierdza, że ​​jeśli zadeklarujesz którekolwiek z

  1. skopiować konstruktora
  2. operator przypisania kopii
  3. burzyciel

wtedy powinieneś zadeklarować wszystkie trzy. Wynikało z obserwacji, że potrzeba przejęcia znaczenia operacji kopiowania prawie zawsze wynikała z tego, że klasa dokonała pewnego rodzaju zarządzania zasobami, co prawie zawsze sugerowało, że

  • jakiekolwiek zarządzanie zasobami było wykonywane w jednej operacji kopiowania, prawdopodobnie wymagało to wykonania w drugiej operacji kopiowania i

  • destruktor klasy również uczestniczyłby w zarządzaniu zasobem (zwykle zwalniając go). Klasycznym zasobem, którym można zarządzać, była pamięć, i dlatego wszystkie klasy Standard Library to zarządzać pamięcią (np. kontenery STL, które wykonują dynamiczne zarządzanie pamięcią) wszystkie deklarują "wielką trójkę": zarówno operacje kopiowania, jak i destruktor.

Konsekwencja Reguły Trzech jest to, że obecność destruktora deklarowanego przez użytkownika wskazuje, że prosta mądra kopia elementu jest mało prawdopodobna, aby była odpowiednia dla operacji kopiowania w klasie. To z kolei sugeruje, że jeśli klasa deklaruje destruktor, operacje kopiowania prawdopodobnie nie powinny być generowane automatycznie, ponieważ nie postąpią właściwie. W chwili przyjęcia C ++ 98 znaczenie tej linii rozumowania nie zostało w pełni docenione, więc w C ++ 98 istnienie deklarowanego destruktora nie miało wpływu na gotowość kompilatorów do generowania operacji kopiowania. Tak jest nadal w C ++ 11, ale tylko dlatego, że ograniczenie warunków, w których generowane są operacje kopiowania, złamałoby zbyt wiele starszego kodu.

Jak mogę zapobiec kopiowaniu moich obiektów?

Deklaracja konstruktora kopiowania i operatora przypisania kopiowania jako specyfikatora dostępu prywatnego.

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);
}

W C ++ 11 można również zadeklarować usunięcie konstruktora kopiowania i przypisania

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

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


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

19
2018-01-12 09:54





Wiele istniejących odpowiedzi dotyka już konstruktora kopiowania, operatora przypisania i destruktora. Jednak w post C ++ 11 wprowadzenie semantycznego ruchu może rozszerzyć to poza 3.

Niedawno Michael Claisse wygłosił przemówienie poruszające ten temat: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Cononical-Class


9
2018-01-07 05:38





Zasada trzech w C ++ jest podstawową zasadą projektowania i rozwijania trzech wymagań, które jeśli istnieje jednoznaczna definicja w jednej z następujących funkcji składowych, wówczas programista powinien wspólnie zdefiniować pozostałe dwie funkcje członu. Mianowicie następujące trzy funkcje składowe są niezbędne: destruktor, konstruktor kopiowania, operator przypisania kopiowania.

Kopiuj konstruktor w C ++ jest specjalnym konstruktorem. Służy do budowania nowego obiektu, który jest nowym obiektem równoważnym kopii istniejącego obiektu.

Operator przypisania kopii jest specjalnym operatorem przypisania, który jest zwykle używany do określania istniejącego obiektu innym osobom tego samego typu obiektu.

Istnieją szybkie przykłady:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;

5
2017-08-12 04:27



Cześć, twoja odpowiedź nie dodaje nic nowego. Inni zajmują się tym tematem na znacznie więcej głębokości, a dokładniej - twoja odpowiedź jest przybliżona, a w rzeczywistości błędna w niektórych miejscach (a mianowicie, że nie ma tu "obowiązkowej", to "prawdopodobnie powinno"). Naprawdę nie warto poświęcić czasu na zamieszczanie tego rodzaju odpowiedzi na pytania, na które zostały już dokładnie udzielone odpowiedzi. Chyba, że ​​masz nowe rzeczy do dodania. - Mat
Są też cztery szybkie przykłady, które są jakoś związany z dwa z trzy o której mówi Reguła Trzech. Za dużo zamieszania. - anatolyg