Có phải tất cả các đối tượng trong C ++ đều có thể thay đổi nếu không được nêu khác?


8

Có phải mọi đối tượng trong C ++ đều có thể thay đổi nếu không được nêu khác?

Trong Python và Javascript tôi không thể thay đổi chuỗi, tuple, unicodes. Tôi đã tự hỏi nếu có một cái gì đó trong C ++ là bất biến hoặc mọi đối tượng đều có thể thay đổi và tôi phải sử dụng constloại vòng loại để làm cho nó bất biến.


2
bạn có ý nghĩa gì với "theo mặc định"?
Bовић

Rằng tất cả các đối tượng trong C ++ đều có thể thay đổi nếu không được nêu khác nhau.
GM

1
Vâng, đúng vậy. Lưu ý rằng đây không giống như từ khóa "có thể thay đổi".
Olav

3
Câu hỏi trong thân câu hỏi là một câu hỏi khác với câu hỏi trong tiêu đề?
dùng253751

@immibis chỉ để thêm một chút tranh chấp
GM

Câu trả lời:


18

Bất biến đã được hiểu rõ trong một thời gian. Python, Java và C ++ có các mô hình bộ nhớ khác nhau khiến việc so sánh trực tiếp trở nên khó khăn. Tác giả của bài báo mà bạn trích dẫn ban đầu dường như không biết C ++.

Như trong Python, Java và hầu hết các ngôn ngữ đa mô hình, C và C ++ cho phép khả năng biến đổi theo mặc định. Đây là những gì lập trình viên thường muốn: nếu tôi có một String xbiến, tôi muốn có thể gán một giá trị mới x = "foo".

Hệ thống const trong C và C ++ cho phép rất nhiều tính bất biến sắc thái thiếu trong Python, Java và thậm chí là Scala. Nếu một hàm C ++ mất một const std::string&hoặc a const char*, nó hứa hẹn (và trình biên dịch đảm bảo, ở một mức độ nào đó), rằng hàm này sẽ không (không thể!) Thay đổi nội dung của chuỗi đó. Cho một đối tượng const, chúng ta chỉ có thể gọi các phương thức của đối tượng đó cũng được đánh dấu là const. Nếu một lớp C ++ chỉ có các thành viên công khai const, thì đối tượng đó là bất biến.

Tuy nhiên, điều này đôi khi gây nhầm lẫn vì trong các đối tượng C và C ++ là các vị trí bộ nhớ và các biến là tên cho các vị trí bộ nhớ. Ngược lại, các biến trong Python và Java là tên của các con trỏ tới các đối tượng. Trong một ngôn ngữ có ngữ nghĩa tham chiếu, x = ycó nghĩa là tạo ra điểm x cho cùng một đối tượng như y. Vì chúng tôi chỉ sao chép con trỏ, điều này là có thể với các đối tượng bất biến. Trong một ngôn ngữ có ngữ nghĩa giá trị như C ++, điều đó có nghĩa là Cập nhật nội dung xvới nội dung của yTrực tiếp. Do đó, nếu việc gán lại một biến được mong muốn trong C hoặc C ++, biến đó có thể không có kiểu const. Để làm điều này với các đối tượng bất biến, chúng ta sẽ phải sử dụng một con trỏ rõ ràng.

Việc Java và Python sử dụng các đối tượng chuỗi bất biến là một quyết định thiết kế cơ bản, nhưng nó không được kết nối trực tiếp với lợi ích của tính bất biến trong môi trường đa luồng. Một lý do là chuỗi ký tự trong mã nguồn có thể được gộp lại làm giảm số lượng đối tượng. Điều này cũng có thể có trong C / C ++. Trong C ++, nghĩa đen "foo"có loại const char[4](char thứ 4 là chấm dứt '\0'). Một lý do khác là các mục trong bộ và khóa trong dicts / maps không được thay đổi. Vì các chuỗi được sử dụng phổ biến như các khóa dict (hầu hết các đối tượng Python là một dict), tính không thay đổi sẽ loại bỏ một nguồn lỗi phổ biến. Trong Java, một lý do khác cho các chuỗi bất biến là mô hình bảo mật Java. Tất cả những lý do này hoàn toàn không liên quan đến đa luồng.

Nếu Java được xây dựng với ý tưởng bất biến, ngôn ngữ sẽ có vẻ rất khác. Mặc dù được truyền cảm hứng chặt chẽ bởi C ++, các nhà thiết kế đã cố gắng tạo ra một ngôn ngữ đơn giản hơn nhiều, loại bỏ const là một bước như vậy. Điều Java tương đương với tham chiếu const C ++ là một bộ điều hợp hoặc trình trang trí thực hiện bất kỳ phương thức đột biến nào throws new NotImplementedException()và chuyển tiếp các phương thức không biến đổi gọi đến bộ sưu tập thực tế. Thực tế là tất cả các giao diện bộ sưu tập java.util đều ngụ ý khả năng biến đổi là một dấu hiệu rõ ràng mà họ đã không cố gắng cho một ngôn ngữ đầu tiên không thay đổi.

Giải pháp mà Java đưa ra để giải quyết các vấn đề tương tranh không phải là bất biến, mà là khóa phổ biến. Mỗi đối tượng chứa một mutex có thể được sử dụng cho synchronizedcác khối hoặc toàn bộ phương thức. Hóa ra, điều đó không tốt cho hiệu suất, không mở rộng rất tốt và khá dễ bị lỗi - bạn vẫn phải đối phó với trạng thái toàn cầu có thể thay đổi.


13
Đây là điều mà các lập trình viên thường muốn => đó là yêu cầu gây tranh cãi. Kinh nghiệm của tôi thực sự ngược lại: hầu hết các biến cục bộ là bất biến, với biến đôi khi có thể thay đổi. Các ngôn ngữ như Rust (trong đó khả năng biến đổi đòi hỏi phải có muttừ khóa và khi trình biên dịch không cần thiết được gắn cờ bởi trình biên dịch) có xu hướng ủng hộ giả thuyết của tôi: trong Rust, bạn có nhiều letràng buộc hơn nhiều so với các liên kết let mut.
Matthieu M.

1
@MatthieuM.: Điều đó một phần vì for x in 1..10không sử dụng let mutđể xác định biến vòng lặp, nhưng rõ ràng nó đòi hỏi một số khả năng biến đổi (chỉ ở cấp độ vòng lặp, không phải bên trong thân vòng lặp).
MSalters

@MSalters: Tất nhiên, nhưng C ++ cũng có cú pháp phạm vi phạm vi :)
Matthieu M.

4
@MSalters Tôi mô tả đặc điểm đó là ràng buộc 10 biến được gọi là x với 10 giá trị, không biến đổi một biến
Caleth

1
@MatthieuM. Tôi đồng ý rằng tính bất biến là tốt, nhưng trong C ++, nó đơn giản không phải lúc nào cũng là một lựa chọn, hoặc ít nhất là quá khó xử. Ví dụ: khởi tạo một biến const trong nhiều bước yêu cầu chúng ta thực hiện điều đó trong một hàm riêng biệt nơi đối tượng đó chưa const. Ít nhất C ++ 11 chúng ta hãy sử dụng lambdas được gọi ngay lập tức như const T o = [&]{T o; o.init(...); return o;}();yay yay cho các API không được thiết kế với ý tưởng bất biến.
amon

9

Hầu hết. Bản thân ngôn ngữ coi mọi thứ là có thể thay đổi trừ khi bạn sử dụng const, nhưng vẫn có thể xây dựng các đối tượng bất biến mà không cần nói với trình biên dịch là bất biến, chỉ đơn giản là không cung cấp cách để biến đổi chúng.

Ví dụ: lấy lớp này:

class MyClass {
    int value;
public:

    MyClass(int v) : value(v) {}
    int getValue() {return value;}

    MyClass &operator=(const MyClass &other) = delete;
};

Các trường hợp MyClasslà bất biến, bởi vì không có cách nào để biến đổi chúng - nhưng không nơi nào trong mã là nó thực sự tuyên bố chúng là bất biến. (Đây là cùng một cách mà các thể hiện của Stringlớp Java là bất biến)


(Tôi đang bị rỉ sét) Việc deletesử dụng toán tử gán sao chép ở đây cũng đảm bảo người ta không thể gán từ tham chiếu giá trị thông qua op chuyển nhượng op?
gạch dưới

Và việc phá hủy đối tượng bằng một lệnh gọi hàm hủy rõ ràng và sau đó tạo lại nó với vị trí mới được tính là "đột biến"?
Peter Green

1
@underscore_d Nó được tính là người dùng xác định toán tử cho các mục đích không hoàn toàn thêm một nhiệm vụ di chuyển, vâng
Caleth

Tôi tự hỏi liệu một cơ chế để cho trình biên dịch biết rằng các đối tượng của một lớp nhất định luôn luôn bất biến có thể dẫn đến tối ưu hóa tốt hơn. Ví dụ, sẽ không cần thiết để làm mới các đối tượng tiền mặt. Trong ví dụ cụ thể này (và nói chung trong các lớp theo mẫu này) có thể sẽ giúp khai báo valueconst và khai báo getValue()hàm const.
Peter - Tái lập Monica

1
@PeterGreen Không, điều đó được tính là phá hủy đối tượng và tạo đối tượng mới.
dùng253751

2

Không, có một ngoại lệ: Lambdas và bằng cách mở rộng, các đối tượng họ chụp là bất biến theo mặc định:

int i = 0;

[i]{//capturing the value i
    i++; //does not compile, i is const
};

[i]() mutable{ //make the lambda mutable
    i++; //compiles fine
};

Vì vậy, thay vì thêm constđể làm cho nó bất biến, bạn thêm mutableđể làm cho nó không const.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.