Tại sao không phải là `void *` 'được đúc ngầm trong C ++?


30

Trong C, không cần phải chuyển void *sang bất kỳ loại con trỏ nào khác, nó luôn được quảng bá một cách an toàn. Tuy nhiên, trong C ++, đây không phải là trường hợp. Ví dụ,

int *a = malloc(sizeof(int));

hoạt động trong C, nhưng không hoạt động trong C ++. (Lưu ý: Tôi biết rằng bạn không nên sử dụng malloctrong C ++, hoặc cho rằng vấn đề new, và thay vào đó nên thích con trỏ thông minh và / hoặc STL, điều này được yêu cầu đơn thuần vì tò mò) Tại sao chuẩn C ++ không cho phép diễn viên tiềm ẩn này, trong khi tiêu chuẩn C làm gì?


3
long *a = malloc(sizeof(int));Rất tiếc, ai đó quên chỉ thay đổi một loại!
Doval

4
@Doval: Điều đó vẫn dễ dàng được sửa chữa bằng cách sử dụng sizeof(*a)thay thế.
wolfPack88

3
Tôi tin rằng quan điểm của @ ratchetfreak là lý do C thực hiện chuyển đổi ngầm định này là vì mallockhông thể trả về một con trỏ cho loại được phân bổ. newlà C ++ không trả về một con trỏ tới kiểu được phân bổ, do đó, mã C ++ được viết đúng sẽ không bao giờ có bất kỳ void *s nào để truyền.
Gort Robot

3
Bên cạnh đó, nó không phải là bất kỳ loại con trỏ khác. Chỉ con trỏ dữ liệu cần áp dụng.
Ded repeatator

3
@ wolfPack88 bạn có lịch sử sai. C ++ đã có void, C thì không . Khi từ khóa / ý tưởng đó được thêm vào C, họ đã thay đổi nó cho phù hợp với nhu cầu của C. Đó là một thời gian ngắn sau khi loại con trỏ bắt đầu được kiểm tra ở tất cả . Xem nếu bạn có thể tìm thấy cuốn sách nhỏ mô tả K & R C trực tuyến hoặc bản sao cổ điển của văn bản lập trình C như C Primer của Tập đoàn Waite . ANSI C đã có đầy đủ, các tính năng được nhập hoặc lấy cảm hứng từ C ++ và K & R C đơn giản hơn nhiều. Vì vậy, chính xác hơn là C ++ đã mở rộng C như nó tồn tại vào thời điểm đó, và C mà bạn biết đã bị tước khỏi C ++.
JDługosz

Câu trả lời:


39

Bởi vì các chuyển đổi kiểu ẩn thường không an toàn và C ++ có lập trường an toàn hơn để gõ so với C.

C thường sẽ cho phép chuyển đổi ngầm định, ngay cả khi hầu hết các khả năng là chuyển đổi là một lỗi. Đó là bởi vì C giả định rằng lập trình viên biết chính xác những gì họ đang làm, và nếu không, đó là vấn đề của lập trình viên, không phải vấn đề của nhà biên dịch.

C ++ thường sẽ không cho phép những thứ có khả năng xảy ra lỗi và yêu cầu bạn phải nêu rõ ý định của mình bằng một loại diễn viên. Đó là bởi vì C ++ đang cố thân thiện với lập trình viên.

Bạn có thể hỏi làm thế nào nó trở nên thân thiện khi nó thực sự yêu cầu bạn gõ nhiều hơn.

Chà, bạn thấy đấy, bất kỳ dòng mã đã cho nào, trong bất kỳ chương trình nào, trong bất kỳ ngôn ngữ lập trình nào, thường sẽ được đọc nhiều lần hơn so với nó sẽ được viết (*). Vì vậy, dễ đọc là quan trọng hơn nhiều so với dễ viết. Và khi đọc, có bất kỳ chuyển đổi có khả năng không an toàn nào nổi bật bằng các loại phôi rõ ràng sẽ giúp hiểu được những gì đang diễn ra và có một mức độ chắc chắn nhất định rằng những gì đang xảy ra trong thực tế là những gì đang xảy ra.

Bên cạnh đó, sự bất tiện khi phải nhập dàn diễn viên rõ ràng là không đáng kể so với sự bất tiện hàng giờ sau khi khắc phục sự cố để tìm lỗi gây ra bởi một nhiệm vụ sai lầm mà bạn có thể đã được cảnh báo, nhưng chưa bao giờ.

(*) Lý tưởng là nó sẽ chỉ được viết một lần, nhưng nó sẽ được đọc mỗi khi ai đó cần xem lại để xác định sự phù hợp của nó để sử dụng lại và mỗi khi có sự cố xảy ra và mỗi khi ai đó cần thêm mã gần nó, và sau đó mỗi khi có sự cố về mã gần đó, v.v. Điều này đúng trong mọi trường hợp ngoại trừ các tập lệnh "viết một lần, chạy, sau đó vứt đi" và do đó, không có gì lạ khi hầu hết các ngôn ngữ tập lệnh đều có một cú pháp tạo điều kiện dễ dàng viết mà hoàn toàn không để dễ đọc. Bạn đã bao giờ nghĩ rằng perl là hoàn toàn không thể hiểu được? Bạn không cô đơn. Hãy nghĩ về những ngôn ngữ như ngôn ngữ "chỉ viết".


C về cơ bản là một bước trên mã máy. Tôi có thể ân xá C cho những thứ như thế này.
Qix

8
Các chương trình (bất kể ngôn ngữ) có nghĩa là để đọc. Hoạt động rõ ràng nổi bật.
Matthieu M.

10
Điều đáng chú ý là, các diễn viên thông qua void*sẽ không an toàn hơn trong C ++, bởi vì theo cách mà các tính năng OOP nhất định được triển khai trong C ++, con trỏ tới cùng một đối tượng có thể có giá trị khác nhau tùy thuộc vào loại con trỏ.
hyde

@MatthieuM. rất đúng. Cảm ơn vì đã thêm nó, nó đáng để trở thành một phần của câu trả lời.
Mike Nakis

1
@MatthieuM.: Ah, nhưng bạn thực sự không muốn phải làm mọi thứ rõ ràng. Khả năng đọc không được tăng cường bằng cách có nhiều hơn để đọc. Mặc dù trong thời điểm này, sự cân bằng rõ ràng là rõ ràng.
Ded

28

Đây là những gì Stroustrup nói :

Trong C, bạn có thể chuyển đổi một khoảng trống * thành T *. Điều này không an toàn

Sau đó, anh tiếp tục đưa ra một ví dụ về cách void * có thể nguy hiểm và nói:

... Do đó, trong C ++, để có được T * từ khoảng trống * bạn cần một dàn diễn viên rõ ràng. ...

Cuối cùng, ông lưu ý:

Một trong những cách sử dụng phổ biến nhất của chuyển đổi không an toàn này trong C là gán kết quả của malloc () cho một con trỏ phù hợp. Ví dụ:

int * p = malloc (sizeof (int));

Trong C ++, sử dụng toán tử mới an toàn kiểu:

int * p = new int;

Ông đi sâu vào chi tiết hơn về điều này trong Thiết kế và tiến hóa của C ++ .

Vì vậy, câu trả lời rút ra: Nhà thiết kế ngôn ngữ tin rằng đó là một mẫu không an toàn, và do đó đã khiến nó trở thành bất hợp pháp và cung cấp các cách khác để hoàn thành những gì mẫu thường được sử dụng cho.


1
Liên quan đến mallocví dụ, đáng chú ý là malloc(rõ ràng) sẽ không gọi hàm tạo, vì vậy nếu đó là loại lớp bạn đang phân bổ bộ nhớ cho, việc có thể truyền vào loại lớp sẽ gây hiểu lầm.
chbaker0

Đây là một câu trả lời rất tốt, được cho là tốt hơn của tôi. Tôi chỉ không thích cách tiếp cận "tranh luận từ chính quyền".
Mike Nakis

"new int" - wow, với tư cách là một người mới sử dụng C ++, tôi đã gặp khó khăn khi nghĩ cách thêm một loại cơ sở vào đống, và tôi thậm chí không biết bạn có thể làm điều đó. -1 sử dụng cho malloc.
Katana314

3
@MikeNakisFWIW, ý định của tôi là bổ sung cho câu trả lời của bạn. Trong trường hợp này, câu hỏi là "tại sao họ làm như vậy", vì vậy tôi cho rằng nghe từ nhà thiết kế chính là hợp lý.
Gort Robot

11

Trong C, không cần phải tạo khoảng trống * cho bất kỳ loại con trỏ nào khác, nó luôn được quảng bá một cách an toàn.

Nó luôn được quảng bá, có, nhưng hầu như không an toàn .

C ++ vô hiệu hóa chính xác hành vi này vì nó cố gắng có một hệ thống loại an toàn hơn C và hành vi này không an toàn.


Nhìn chung, hãy xem xét 3 cách tiếp cận này để chuyển đổi loại:

  1. buộc người dùng viết mọi thứ ra, vì vậy tất cả các chuyển đổi đều rõ ràng
  2. giả sử người dùng biết họ đang làm gì và chuyển đổi bất kỳ loại nào sang bất kỳ loại nào khác
  3. triển khai một hệ thống loại hoàn chỉnh với các tổng quát an toàn kiểu (mẫu, biểu thức mới), toán tử chuyển đổi do người dùng xác định rõ ràng và chỉ chuyển đổi rõ ràng những thứ mà trình biên dịch không thể thấy là an toàn ngầm

Chà, 1 là xấu xí và là một trở ngại thực tế để hoàn thành bất cứ điều gì, nhưng thực sự có thể được sử dụng khi cần sự chăm sóc tuyệt vời. C đại khái đã chọn cho 2, dễ thực hiện hơn và C ++ cho 3, khó thực hiện hơn nhưng an toàn hơn.


Đó là một con đường dài khó khăn để lên 3, với các ngoại lệ được thêm vào sau và các mẫu sau vẫn còn.
JDługosz

Chà, các mẫu không thực sự cần thiết cho một hệ thống loại an toàn hoàn chỉnh như vậy: Các ngôn ngữ dựa trên Hindley-Milner hòa hợp với nhau mà không có chúng, hoàn toàn không có chuyển đổi ngầm định và cũng không cần phải viết ra các loại một cách rõ ràng. (Tất nhiên, những ngôn ngữ dựa trên loại tẩy xoá / thu gom rác thải / đa hình cao kinded, điều mà C ++ chứ không phải tránh.)
leftaroundabout

1

Theo định nghĩa, một con trỏ void có thể trỏ đến bất cứ thứ gì. Bất kỳ con trỏ nào cũng có thể được chuyển đổi thành một con trỏ trống và do đó, bạn sẽ có thể chuyển đổi trở lại đến cùng một giá trị. Tuy nhiên, con trỏ đến các loại khác có thể có các ràng buộc, chẳng hạn như các hạn chế căn chỉnh. Ví dụ, hãy tưởng tượng một kiến ​​trúc nơi các ký tự có thể chiếm bất kỳ địa chỉ bộ nhớ nào nhưng số nguyên phải bắt đầu trên các ranh giới địa chỉ chẵn. Trong một số kiến ​​trúc nhất định, các con trỏ nguyên thậm chí có thể đếm 16, 32 hoặc 64 bit tại một thời điểm để một char * thực sự có thể có nhiều giá trị số của int * trong khi chỉ đến cùng một vị trí trong bộ nhớ. Trong trường hợp này, chuyển đổi từ khoảng trống * thực sự sẽ làm tròn các bit không thể phục hồi và do đó không thể đảo ngược.

Nói một cách đơn giản, con trỏ void có thể trỏ đến bất cứ thứ gì, kể cả những thứ mà các con trỏ khác có thể không có khả năng trỏ đến. Do đó, việc chuyển đổi sang con trỏ void là an toàn nhưng không phải là cách khác.

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.