Câu hỏi Quy tắc ba là gì?


  • Cái gì sao chép một đối tượng nghĩa là?
  • Là những gì -bộ tạo bản sao và toán tử gán bản sao?
  • Khi nào tôi cần tự khai báo?
  • Làm cách nào để ngăn không cho các đối tượng của tôi bị sao chép?

1844
2017-11-13 13:27


gốc


Xin vui lòng đọc toàn bộ chủ đề này và các c++-faq tag wiki trước khi bạn bỏ phiếu để đóng. - sbi
@Binary: Ít nhất dành thời gian để đọc các cuộc thảo luận bình luận trước bạn bỏ phiếu. Các văn bản được sử dụng để được đơn giản hơn nhiều, nhưng Fred đã được yêu cầu mở rộng trên nó. Ngoài ra, trong khi đó là bốn câu hỏi theo ngữ pháp, nó thực sự chỉ là một câu hỏi với một vài khía cạnh cho nó. (Nếu bạn không đồng ý với điều đó, thì hãy chứng minh POV của bạn bằng cách trả lời từng câu hỏi đó và cho chúng tôi bỏ phiếu cho kết quả.) - sbi
Fred, đây là một bổ sung thú vị cho câu trả lời của bạn về C ++ 1x: stackoverflow.com/questions/4782757/…. Chúng ta xử lý việc này thế nào đây? - sbi
Liên quan: Luật của The Big Two - Nemanja Trifunovic
Hãy nhớ rằng, như của C ++ 11, tôi nghĩ rằng điều này đã được nâng cấp lên quy tắc của năm, hoặc một cái gì đó như thế. - paxdiablo


Các câu trả lời:


Giới thiệu

C ++ xử lý các biến của các kiểu do người dùng định nghĩa với ngữ nghĩa giá trị. Điều này có nghĩa là các đối tượng được sao chép hoàn toàn trong các ngữ cảnh khác nhau, và chúng ta nên hiểu những gì "sao chép một đối tượng" thực sự có nghĩa là.

Chúng ta hãy xem xét một ví dụ đơn giản:

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

(Nếu bạn đang bối rối bởi name(name), age(age) phần, điều này được gọi là danh sách khởi tạo thành viên.)

Chức năng thành viên đặc biệt

Sao chép một person vật? Các main chức năng cho thấy hai kịch bản sao chép riêng biệt. Khởi tạo person b(a); được thực hiện bởi -bộ tạo bản sao. Công việc của nó là xây dựng một đối tượng mới dựa trên trạng thái của một đối tượng hiện có. Bài tập b = a được thực hiện bởi toán tử gán bản sao. Công việc của nó thường phức tạp hơn một chút, vì đối tượng đích đã ở trong một số trạng thái hợp lệ cần được xử lý.

Vì chúng tôi đã tuyên bố không phải là nhà xây dựng bản sao cũng không phải là toán tử gán (cũng không phải là destructor), chúng được xác định hoàn toàn cho chúng ta. Trích dẫn từ tiêu chuẩn:

[...] copy constructor và copy assignment operator, [...] và destructor là các hàm thành viên đặc biệt.   [ chú thích: Việc triển khai sẽ ngầm khai báo các hàm thành viên này   đối với một số loại lớp khi chương trình không khai báo rõ ràng chúng.   Việc triển khai sẽ ngầm định nghĩa chúng nếu chúng được sử dụng. [...] ghi chú kết thúc ]   [n3126.pdf phần 12 §1]

Theo mặc định, sao chép một đối tượng có nghĩa là sao chép các thành viên của nó:

Trình tạo bản sao được xác định rõ ràng cho một lớp không phải công đoàn X thực hiện một bản sao thành viên của các lớp con của nó.   [n3126.pdf phần 12.8 §16]

Toán tử gán bản sao được xác định rõ ràng cho một lớp không công đoàn X thực hiện gán bản sao thành viên   của subobjects của nó.   [n3126.pdf phần 12.8 §30]

Định nghĩa ngầm định

Hàm thành viên đặc biệt được xác định ngầm cho person trông như thế này:

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

Sao chép thành viên là chính xác những gì chúng ta muốn trong trường hợp này: name và age được sao chép, vì vậy chúng tôi có được độc lập, độc lập person vật. Trình phá hủy ngầm định luôn luôn rỗng. Điều này cũng tốt trong trường hợp này vì chúng tôi không thu được bất kỳ tài nguyên nào trong hàm tạo. Các destructors của các thành viên được ngầm gọi sau person destructor được hoàn thành:

Sau khi thực hiện phần thân của destructor và phá hủy bất kỳ đối tượng tự động nào được phân bổ bên trong cơ thể,   một destructor cho lớp X gọi các destructors cho các thành viên [...] trực tiếp của X   [n3126.pdf 12.4 §6]

Quản lý tài nguyên

Vậy khi nào chúng ta nên khai báo các hàm thành viên đặc biệt đó một cách rõ ràng? Khi lớp học của chúng tôi quản lý tài nguyên, đó là, khi một đối tượng của lớp là chịu trách nhiệm cho tài nguyên đó. Điều đó thường có nghĩa là tài nguyên là mua trong hàm tạo (hoặc được truyền vào hàm tạo) và phát hành trong destructor.

Chúng ta hãy quay ngược thời gian để chuẩn C ++. Không có thứ gì như std::stringvà các lập trình viên đã yêu con trỏ. Các person lớp học có thể trông như thế này:

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

Thậm chí ngày nay, mọi người vẫn viết các lớp theo phong cách này và gặp rắc rối: "Tôi đã đẩy một người vào một véc tơ và giờ tôi đã bị lỗi bộ nhớ điên rồ!" Hãy nhớ rằng theo mặc định, việc sao chép một đối tượng có nghĩa là sao chép các thành viên của nó, nhưng sao chép name thành viên chỉ sao chép một con trỏ, không phải mảng ký tự mà nó trỏ tới! Điều này có một số hiệu ứng khó chịu:

  1. Thay đổi qua a có thể được quan sát qua b.
  2. Một lần b bị phá hủy, a.name là một con trỏ lơ lửng.
  3. Nếu a bị phá hủy, xóa sản lượng con trỏ lơ lửng hành vi không xác định.
  4. Kể từ khi chuyển nhượng không đưa vào tài khoản những gì name chỉ vào trước bài tập, sớm hay muộn bạn sẽ bị rò rỉ bộ nhớ khắp nơi.

Định nghĩa rõ ràng

Vì sao chép thành viên không có hiệu ứng mong muốn, chúng ta phải xác định hàm tạo bản sao và toán tử gán bản sao một cách rõ ràng để tạo các bản sao sâu của mảng ký tự:

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

Lưu ý sự khác biệt giữa khởi tạo và gán: chúng ta phải phá bỏ trạng thái cũ trước khi gán cho name để ngăn chặn rò rỉ bộ nhớ. Ngoài ra, chúng tôi phải bảo vệ chống lại việc tự chuyển nhượng biểu mẫu x = x. Nếu không có kiểm tra đó, delete[] name sẽ xóa mảng chứa nguồn chuỗi, bởi vì khi bạn viết x = xcả hai this->name và that.name chứa cùng một con trỏ.

An toàn ngoại lệ

Thật không may, giải pháp này sẽ thất bại nếu new char[...] ném một ngoại lệ do cạn kiệt bộ nhớ. Một giải pháp có thể là giới thiệu một biến cục bộ và sắp xếp lại các câu lệnh:

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

Điều này cũng đảm nhiệm việc tự chuyển nhượng mà không cần kiểm tra rõ ràng. Một giải pháp thậm chí còn mạnh mẽ hơn cho vấn đề này là thành ngữ sao chép và hoán đổi, nhưng tôi sẽ không đi vào chi tiết về sự an toàn ngoại lệ ở đây. Tôi chỉ đề cập đến ngoại lệ để thực hiện các điểm sau: Viết các lớp học quản lý tài nguyên là khó.

Tài nguyên không thể sao chép

Một số tài nguyên không thể hoặc không nên được sao chép, chẳng hạn như xử lý tệp hoặc mutexes. Trong trường hợp đó, chỉ cần khai báo hàm tạo bản sao và sao chép toán tử gán như private mà không đưa ra định nghĩa:

private:

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

Ngoài ra, bạn có thể kế thừa từ boost::noncopyable hoặc khai báo chúng là đã bị xóa (C ++ 0x):

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

Quy tắc của ba

Đôi khi bạn cần triển khai một lớp quản lý tài nguyên. (Không bao giờ quản lý nhiều tài nguyên trong một lớp duy nhất, điều này sẽ chỉ dẫn đến đau.) Trong trường hợp đó, hãy nhớ quy tắc của ba:

Nếu bạn cần khai báo rõ ràng một trong hai destructor,   tự tạo bản sao hoặc tự sao chép toán tử gán   bạn có thể cần khai báo rõ ràng cả ba.

(Thật không may, "quy tắc" này không được thực thi bởi tiêu chuẩn C ++ hoặc bất kỳ trình biên dịch nào mà tôi biết).

Khuyên bảo

Hầu hết thời gian, bạn không cần tự quản lý tài nguyên, bởi vì một lớp học hiện tại như std::string đã làm điều đó cho bạn. Chỉ cần so sánh mã đơn giản bằng cách sử dụng std::string hội viên để thay thế phức tạp và dễ bị lỗi bằng cách sử dụng char* và bạn nên bị thuyết phục. Miễn là bạn tránh xa các thành viên con trỏ thô, quy tắc của ba không có khả năng liên quan đến mã của riêng bạn.


1517
2017-11-13 13:27



Fred, tôi sẽ cảm thấy tốt hơn về cuộc bỏ phiếu của tôi nếu (A) bạn sẽ không viết ra bài tập được thực thi nặng trong mã có thể sao chép được và thêm một ghi chú nói rằng nó sai và nhìn vào chỗ khác; hoặc sử dụng c & s trong mã hoặc chỉ bỏ qua việc thực hiện tất cả các thành viên này (B) bạn sẽ rút ngắn nửa đầu, điều này ít có liên quan đến RoT; (C) bạn sẽ thảo luận về việc giới thiệu ngữ nghĩa di chuyển và điều đó có ý nghĩa gì đối với RoT. - sbi
Nhưng sau đó bài viết nên được thực hiện C / W, tôi nghĩ. Tôi thích rằng bạn giữ các cụm từ chủ yếu là chính xác (nghĩa là bạn nói "sao chép toán tử gán ", và rằng bạn không gõ vào cái bẫy chung mà nhiệm vụ không thể ngụ ý một bản sao). - Johannes Schaub - litb
@ Prasoon: Tôi không nghĩ rằng cắt ra một nửa câu trả lời sẽ được xem là "chỉnh sửa công bằng" của câu trả lời không phải là CW. - sbi
Ngoài ra, tôi có thể đã overread nó, nhưng bạn không đề cập rằng các nhà điều hành bản sao assingment nên kiểm tra danh tính trước khi làm bất cứ điều gì. - Björn Pollex
Sẽ thật tuyệt nếu bạn cập nhật bài đăng của mình cho C ++ 11 (tức là di chuyển constructor / assignment) - Alexander Malakhov


Các Quy tắc Ba là một quy tắc của C ++, về cơ bản nói

Nếu lớp học của bạn cần bất kỳ

  • một -bộ tạo bản sao,
  • một toán tử gán,
  • hoặc một người hủy diệt,

được xác định rõ ràng, sau đó có thể cần cả ba người họ.

Lý do cho điều này là tất cả ba trong số chúng thường được sử dụng để quản lý tài nguyên và nếu lớp của bạn quản lý tài nguyên, nó thường cần quản lý sao chép cũng như giải phóng.

Nếu không có ngữ nghĩa tốt để sao chép tài nguyên mà lớp của bạn quản lý, thì hãy xem xét cấm sao chép bằng cách khai báo (không định nghĩa) constructor sao chép và toán tử gán như private.

(Lưu ý rằng phiên bản mới sắp tới của tiêu chuẩn C ++ (là C ++ 11) bổ sung thêm ngữ nghĩa di chuyển vào C ++, điều này có khả năng sẽ thay đổi Quy tắc 3. Tuy nhiên, tôi biết quá ít về điều này để viết một phần C ++ 11 về Quy tắc Ba.)


451
2017-11-13 14:22



Một giải pháp khác để ngăn chặn việc sao chép là kế thừa (riêng) từ một lớp không thể được sao chép (như boost::noncopyable). Nó cũng có thể rõ ràng hơn nhiều. Tôi nghĩ rằng C ++ 0x và khả năng để "xóa" chức năng có thể giúp ở đây, nhưng quên cú pháp: / - Matthieu M.
@Matthieu: Vâng, cũng vậy. Nhưng trừ khi noncopyable là một phần của std lib, tôi không coi nó là một cải tiến. (Oh, và nếu bạn quên cú pháp xóa, bạn đã quên mor ethan mà tôi từng biết. :)) - sbi
Mọi cập nhật về Quy tắc Ba và C ++ 1x? - Daan Timmer
@Daan: Xem câu trả lời này. Tuy nhiên, tôi khuyên bạn nên gắn bó với Martinho'S Quy tắc Zero. Với tôi, đây là một trong những quy tắc quan trọng nhất của C ++ được đặt ra trong thập kỷ qua. - sbi
Quy tắc Zero của Martinho hiện đang nằm đây - Diego


Pháp luật của ba người lớn được quy định ở trên.

Một ví dụ dễ hiểu, bằng tiếng Anh đơn giản, về loại vấn đề mà nó giải quyết:

Không phá hủy mặc định

Bạn đã cấp phát bộ nhớ trong hàm tạo của bạn và vì vậy bạn cần phải viết một trình phá hủy để xóa nó. Nếu không, bạn sẽ gây ra rò rỉ bộ nhớ.

Bạn có thể nghĩ rằng đây là công việc được thực hiện.

Vấn đề sẽ là, nếu một bản sao được tạo ra từ đối tượng của bạn, thì bản sao sẽ trỏ đến cùng bộ nhớ với đối tượng gốc.

Một khi, một trong những xóa bộ nhớ trong destructor của nó, người kia sẽ có một con trỏ đến bộ nhớ không hợp lệ (điều này được gọi là một con trỏ lơ lửng) khi nó cố gắng sử dụng nó mọi thứ sẽ nhận được lông.

Do đó, bạn viết một hàm tạo bản sao để nó phân bổ các đối tượng mới các phần bộ nhớ riêng của chúng để tiêu diệt.

Nhà điều hành chuyển nhượng và tạo bản sao

Bạn đã cấp phát bộ nhớ trong hàm dựng của bạn cho một con trỏ thành viên của lớp của bạn. Khi bạn sao chép một đối tượng của lớp này, toán tử gán mặc định và hàm tạo bản sao sẽ sao chép giá trị của con trỏ thành viên này vào đối tượng mới.

Điều này có nghĩa là đối tượng mới và đối tượng cũ sẽ trỏ vào cùng một phần bộ nhớ, vì vậy khi bạn thay đổi nó trong một đối tượng, nó sẽ được thay đổi cho objerct khác. Nếu một đối tượng xóa bộ nhớ này, người kia sẽ tiếp tục cố gắng sử dụng nó - eek.

Để giải quyết điều này, bạn viết phiên bản của riêng bạn của nhà xây dựng bản sao và toán tử gán. Các phiên bản của bạn cấp phát bộ nhớ riêng cho các đối tượng mới và sao chép qua các giá trị mà con trỏ đầu tiên trỏ tới thay vì địa chỉ của nó.


134
2018-05-14 14:22



Vì vậy, nếu chúng ta sử dụng một hàm tạo bản sao thì bản sao được tạo nhưng ở một vị trí bộ nhớ khác hoàn toàn và nếu chúng ta không sử dụng hàm tạo sao chép thì sao chép được tạo nhưng nó trỏ đến cùng một vị trí bộ nhớ. là những gì bạn đang cố gắng để nói? Vì vậy, một bản sao không có bản sao có nghĩa là một con trỏ mới sẽ ở đó nhưng trỏ đến cùng một vị trí bộ nhớ, tuy nhiên nếu chúng ta có một hàm tạo bản sao được người dùng định nghĩa một cách rõ ràng thì chúng ta sẽ có một con trỏ riêng trỏ đến một vị trí bộ nhớ khác nhưng có dữ liệu. - Unbreakable
Xin lỗi, tôi đã trả lời câu hỏi này từ lâu nhưng câu trả lời của tôi dường như không còn ở đây :-( Về cơ bản, có - bạn nhận được nó :-) - Stefan
Làm thế nào để các nguyên tắc etend cho các nhà điều hành gán bản sao? Câu trả lời này sẽ hữu ích hơn nếu thứ 3 trong Quy tắc Ba sẽ được đề cập đến. - DBedrenko
@DBedrenko, "bạn viết một hàm tạo bản sao để nó phân bổ các đối tượng mới các phần bộ nhớ riêng của chúng ..." đây là cùng một nguyên tắc mở rộng đến toán tử gán bản sao. Bạn không nghĩ rằng tôi đã làm cho rõ ràng? - Stefan
@Stefan Có, cảm ơn rất nhiều! - DBedrenko


Về cơ bản nếu bạn có một destructor (không phải là destructor mặc định) nó có nghĩa là lớp mà bạn đã định nghĩa có một số cấp phát bộ nhớ. Giả sử rằng lớp được sử dụng bên ngoài bởi một số mã máy khách hoặc bởi bạn.

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

Nếu MyClass chỉ có một số thành phần được gõ nguyên thủy, toán tử gán mặc định sẽ hoạt động nhưng nếu nó có một số thành viên con trỏ và các đối tượng không có toán tử gán thì kết quả sẽ không thể đoán trước được. Vì vậy chúng ta có thể nói rằng nếu có cái gì đó để xóa trong destructor của một lớp, chúng tôi có thể cần một nhà điều hành bản sao sâu có nghĩa là chúng ta nên cung cấp một nhà xây dựng bản sao và nhà điều hành chuyển nhượng.


37
2017-12-31 19:29





Sao chép một đối tượng có nghĩa là gì? Có một vài cách để bạn có thể sao chép các đối tượng - hãy nói về 2 loại mà bạn có nhiều khả năng đề cập đến - bản sao sâu và bản sao nông.

Vì chúng ta đang ở trong một ngôn ngữ hướng đối tượng (hoặc ít nhất là giả định như vậy), giả sử bạn có một phần bộ nhớ được cấp phát. Vì nó là một ngôn ngữ OO, chúng ta có thể dễ dàng tham khảo các khối bộ nhớ mà chúng ta phân bổ vì chúng thường là các biến nguyên thủy (ints, chars, bytes) hoặc các lớp mà chúng ta định nghĩa được tạo ra từ các kiểu của chúng ta và nguyên thủy. Vì vậy, chúng ta hãy nói rằng chúng ta có một lớp xe như sau:

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

}

Một bản sao sâu là nếu chúng ta khai báo một đối tượng và sau đó tạo một bản sao hoàn toàn riêng biệt của đối tượng ... chúng ta kết thúc với 2 đối tượng trong 2 bộ nhớ hoàn toàn.

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.

Bây giờ chúng ta hãy làm một cái gì đó kỳ lạ. Giả sử car2 được lập trình sai hoặc cố ý nhằm chia sẻ bộ nhớ thực tế mà car1 được tạo ra. (Nó thường là một sai lầm để làm điều này và trong lớp học thường là chăn nó được thảo luận dưới.) Giả vờ rằng bất cứ lúc nào bạn hỏi về car2, bạn đang thực sự giải quyết một con trỏ đến không gian bộ nhớ của car1 ... đó là nhiều hơn hoặc ít hơn những gì một bản sao nông Là.

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

Vì vậy, bất kể bạn đang viết ngôn ngữ nào, hãy cẩn thận về ý nghĩa của bạn khi nói đến việc sao chép các đối tượng vì phần lớn thời gian bạn muốn có một bản sao sâu.

Nhà xây dựng bản sao và toán tử gán bản sao là gì? Tôi đã sử dụng chúng ở trên. Trình tạo bản sao được gọi khi bạn nhập mã như Car car2 = car1;  Về cơ bản nếu bạn khai báo một biến và gán nó trong một dòng, đó là khi hàm tạo bản sao được gọi. Toán tử gán là điều sẽ xảy ra khi bạn sử dụng dấu bằng -car2 = car1;. Để ý car2 không được khai báo trong cùng một tuyên bố. Hai đoạn mã bạn viết cho các hoạt động này có thể rất giống nhau. Trong thực tế, mẫu thiết kế điển hình có chức năng khác mà bạn gọi để thiết lập mọi thứ khi bạn đã hài lòng bản sao / gán ban đầu là hợp pháp - nếu bạn nhìn vào mã tôi viết, các hàm gần như giống nhau.

Khi nào tôi cần tự khai báo? Nếu bạn không viết mã được chia sẻ hoặc sản xuất theo cách nào đó, bạn thực sự chỉ cần khai báo mã khi bạn cần chúng. Bạn cần phải biết những gì ngôn ngữ chương trình của bạn làm gì nếu bạn chọn sử dụng nó 'do tai nạn' và không làm cho một - tức là. bạn nhận được trình biên dịch mặc định. Tôi hiếm khi sử dụng các hàm tạo bản sao cho ví dụ, nhưng các ghi đè của toán tử gán là rất phổ biến. Bạn có biết bạn có thể ghi đè những gì cộng, trừ, vv có nghĩa là tốt?

Làm cách nào để ngăn không cho các đối tượng của tôi bị sao chép? Ghi đè tất cả các cách bạn được phép phân bổ bộ nhớ cho đối tượng của bạn bằng chức năng riêng tư là một khởi đầu hợp lý. Nếu bạn thực sự không muốn mọi người sao chép chúng, bạn có thể làm cho nó công khai và cảnh báo lập trình viên bằng cách ném một ngoại lệ và cũng không sao chép đối tượng.


27
2017-10-17 16:37



Câu hỏi đã được gắn thẻ C ++. Việc trình bày mã giả này ít làm rõ bất cứ điều gì về "Quy tắc của ba" được xác định tốt nhất, và chỉ lan truyền sự nhầm lẫn ở mức tồi tệ nhất. - sehe


Khi nào tôi cần tự khai báo?

Quy tắc của ba tiểu bang rằng nếu bạn tuyên bố bất kỳ

  1. -bộ tạo bản sao
  2. toán tử gán bản sao
  3. người hủy diệt

thì bạn nên khai báo cả ba. Nó phát triển từ quan sát rằng sự cần thiết phải tiếp nhận ý nghĩa của một hoạt động sao chép hầu như luôn luôn bắt nguồn từ lớp thực hiện một số loại quản lý tài nguyên, và hầu như luôn luôn ngụ ý rằng

  • bất kỳ việc quản lý tài nguyên nào được thực hiện trong một thao tác sao chép có thể cần phải được thực hiện trong thao tác sao chép khác và

  • class destructor cũng sẽ tham gia vào việc quản lý tài nguyên (thường là phát hành nó). Tài nguyên cổ điển được quản lý là bộ nhớ và đây là lý do tại sao tất cả các lớp Thư viện chuẩn quản lý bộ nhớ (ví dụ: các hộp chứa STL thực hiện quản lý bộ nhớ động) tất cả tuyên bố “ba lớn”: cả hoạt động sao chép và trình phá hủy.

Hậu quả của Quy tắc Ba là sự hiện diện của một destructor do người dùng khai báo chỉ ra rằng bản sao thành viên đơn giản khôn ngoan không có khả năng thích hợp cho các hoạt động sao chép trong lớp. Điều đó, đến lượt nó, gợi ý rằng nếu một lớp tuyên bố một destructor, các hoạt động sao chép có lẽ không nên được tạo tự động, bởi vì chúng sẽ không làm điều đúng. Vào thời điểm C ++ 98 được chấp nhận, ý nghĩa của dòng lập luận này không được đánh giá đầy đủ, vì vậy trong C ++ 98, sự tồn tại của một người dùng đã khai báo destructor không ảnh hưởng đến sự sẵn sàng để tạo ra các hoạt động sao chép. Điều đó tiếp tục là trường hợp trong C ++ 11, nhưng chỉ vì hạn chế các điều kiện mà theo đó các hoạt động sao chép được tạo ra sẽ phá vỡ quá nhiều mã kế thừa.

Làm cách nào để ngăn không cho các đối tượng của tôi bị sao chép?

Khai báo nhà xây dựng bản sao và nhà điều hành gán bản sao làm thông số truy cập riêng tư.

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

Trong C ++ 11 trở đi, bạn cũng có thể khai báo hàm tạo bản sao và toán tử gán đã xóa

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





Nhiều câu trả lời hiện có đã chạm vào hàm tạo bản sao, toán tử gán và hàm hủy. Tuy nhiên, trong bài C ++ 11, việc giới thiệu di chuyển ngữ nghĩa có thể mở rộng này vượt quá 3.

Gần đây, Michael Claisse đã có một bài nói chuyện liên quan đến chủ đề này: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class


9
2018-01-07 05:38





Quy tắc ba trong C ++ là nguyên tắc cơ bản của thiết kế và sự phát triển của ba yêu cầu rằng nếu có định nghĩa rõ ràng trong một trong các hàm thành viên sau, thì lập trình viên nên xác định hai thành viên khác hoạt động cùng nhau. Cụ thể là ba hàm thành viên sau là không thể thiếu: destructor, constructor sao chép, toán tử gán bản sao.

Copy constructor trong C ++ là một hàm tạo đặc biệt. Nó được sử dụng để xây dựng một đối tượng mới, đó là đối tượng mới tương đương với một bản sao của một đối tượng hiện có.

Sao chép toán tử gán là toán tử gán đặc biệt thường được sử dụng để chỉ định một đối tượng hiện có cho các đối tượng khác cùng loại đối tượng.

Có những ví dụ nhanh:

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



Xin chào, câu trả lời của bạn không thêm bất kỳ điều gì mới. Những người khác bao gồm chủ đề trong nhiều chiều sâu hơn, và chính xác hơn - câu trả lời của bạn là gần đúng và trên thực tế sai ở một số nơi (cụ thể là không có "phải" ở đây; nó "rất có thể nên"). Nó thực sự không có giá trị trong khi bạn đăng loại trả lời này cho các câu hỏi đã được trả lời kỹ lưỡng rồi. Trừ khi bạn có những điều mới để thêm vào. - Mat
Cũng có bốn ví dụ nhanh, bằng cách nào đó có quan hệ với hai của số ba mà Quy tắc Ba đang nói đến. Quá nhiều nhầm lẫn. - anatolyg