Có phải C # cung cấp cho bạn ít dây hơn để treo mình hơn C ++ không? [đóng cửa]


14

Joel Spolsky mô tả C ++ là "đủ dây để tự treo cổ" . Trên thực tế, ông đã tóm tắt "C ++ hiệu quả" của Scott Meyers:

Về cơ bản, đó là một cuốn sách nói rằng, C ++ là một sợi dây đủ để bạn tự treo mình, và sau đó thêm một vài sợi dây, và sau đó là một vài viên thuốc tự tử được ngụy trang thành M & Ms ...

Tôi không có bản sao của cuốn sách, nhưng có nhiều dấu hiệu cho thấy phần lớn cuốn sách liên quan đến cạm bẫy của việc quản lý bộ nhớ có vẻ như sẽ được đưa ra trong C # vì thời gian chạy quản lý các vấn đề đó cho bạn.

Đây là câu hỏi của tôi:

  1. Có phải C # tránh những cạm bẫy được tránh trong C ++ chỉ bằng cách lập trình cẩn thận? Nếu vậy, ở mức độ nào và làm thế nào họ tránh được?
  2. Có những cạm bẫy mới, khác nhau trong C # mà một lập trình viên C # mới nên biết không? Nếu vậy, tại sao không thể tránh được thiết kế của C #?

10
Từ Câu hỏi thường gặp : Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.. Tôi tin rằng điều này đủ điều kiện như một câu hỏi như vậy ...
Oded

@Oded Bạn đang đề cập đến câu hỏi tiêu đề giới hạn nhân vật? Hoặc hơn 3 câu hỏi chính xác hơn trong bài viết của tôi?
alx9r

3
Thành thật mà nói - cả tiêu đề và từng "câu hỏi chính xác hơn".
Oded

3
Tôi đã bắt đầu một cuộc thảo luận về câu hỏi này.
Oded

1
Về câu hỏi thứ 3 đã bị xóa của bạn, sê-ri C # hiệu quả của Bill Wagner (hiện là 3 cuốn sách) đã dạy tôi nhiều hơn về lập trình C # tốt hơn bất cứ điều gì tôi đọc về chủ đề này. Đánh giá của Martins về EC # đúng ở chỗ nó không bao giờ có thể thay thế trực tiếp cho C ++ hiệu quả, nhưng anh ấy đã nghĩ sai rằng nó nên như vậy. Một khi bạn không còn phải lo lắng về những sai lầm dễ dàng , bạn phải chuyển sang những sai lầm khó khăn hơn .
Đánh dấu gian hàng

Câu trả lời:


33

Sự khác biệt cơ bản giữa C ++ và C # bắt nguồn từ hành vi không xác định .

không có gì để làm với việc quản lý bộ nhớ thủ công. Trong cả hai trường hợp, đó là một vấn đề được giải quyết.

C / C ++:

Trong C ++, khi bạn mắc lỗi, kết quả không được xác định.
Hoặc, nếu bạn cố gắng đưa ra một số loại giả định nhất định về hệ thống (ví dụ như tràn số nguyên đã ký), rất có thể chương trình của bạn sẽ không được xác định.

Có thể đọc loạt bài 3 phần này về hành vi không xác định.

Đây là điều làm cho C ++ trở nên nhanh chóng - trình biên dịch không phải lo lắng về những gì xảy ra khi có sự cố, vì vậy nó có thể tránh việc kiểm tra tính chính xác.

C #, Java, v.v.

Trong C #, bạn được đảm bảo rằng nhiều lỗi sẽ xuất hiện trong khuôn mặt của bạn như là ngoại lệ và bạn được đảm bảo nhiều hơn về hệ thống cơ bản.
Đó là một nền tảng rào cản để làm cho C # nhanh như C ++, nhưng nó cũng là một rào cản cơ bản để làm cho C ++ an toàn và nó giúp C # dễ dàng hơn để làm việc và gỡ lỗi.

Mọi thứ khác chỉ là nước thịt.


Tất cả các công cụ không xác định thực sự được xác định bởi việc triển khai, vì vậy nếu bạn tràn vào một số nguyên không dấu trong Visual Studio, bạn sẽ có một ngoại lệ nếu bạn bật cờ trình biên dịch đúng. Bây giờ tôi biết đây là những gì bạn đang nói, nhưng đó không phải là hành vi không xác định , chỉ là mọi người thường không kiểm tra nó. (giống với hành vi thực sự không xác định như toán tử ++, nó được xác định rõ bởi mỗi trình biên dịch). Bạn có thể nói tương tự với C #, chỉ có 1 triển khai - rất nhiều 'hành vi không xác định' nếu bạn chạy trong Mono - ví dụ: bugzilla.xamarin.com/show_orms.cgi?id= 310
gbjbaanb

1
Nó có thực sự được xác định hoặc chỉ được xác định bởi bất kỳ triển khai .net hiện tại nào trên phiên bản Windows hiện tại không? Ngay cả hành vi không xác định c ++ cũng được xác định đầy đủ nếu bạn định nghĩa nó là bất cứ điều gì g ++ làm.
Martin Beckett

6
Tràn đầy số nguyên không dấu không phải là UB. Đó là tràn số nguyên đã ký đó là UB.
DeadMG

6
@gbjbaanb: Giống như DeadMG nói - ký integer overflow được xác định. Nó không được xác định theo cách thực hiện. Những cụm từ đó có ý nghĩa cụ thể trong tiêu chuẩn C ++ và chúng không giống nhau. Đừng phạm sai lầm đó.
dùng541686

1
@CharlesSalvia: Uh, chính xác thì "C ++ giúp tận dụng bộ đệm CPU" dễ dàng hơn so với C # như thế nào? Và loại C ++ nào cung cấp cho bạn bộ nhớ mà bạn không thể có trong C #?
dùng541686

12

Có phải C # tránh những cạm bẫy được tránh trong C ++ chỉ bằng cách lập trình cẩn thận? Nếu vậy, ở mức độ nào và làm thế nào họ tránh được?

Hầu hết nó làm, một số thì không. Và tất nhiên, nó làm cho một số cái mới.

  1. Hành vi không xác định - Cạm bẫy lớn nhất với C ++ là có rất nhiều ngôn ngữ không được xác định. Trình biên dịch theo nghĩa đen có thể làm nổ tung vũ trụ khi bạn làm những việc này, và nó sẽ ổn thôi. Đương nhiên, điều này là không phổ biến, nhưng nó khá phổ biến cho chương trình của bạn để làm việc tốt trên một máy và vì lý do thực sự không tốt không làm việc trên khác. Hoặc tệ hơn, tinh tế hành động khác nhau. C # có một vài trường hợp hành vi không xác định trong đặc điểm kỹ thuật của nó, nhưng chúng hiếm gặp và trong các lĩnh vực ngôn ngữ không thường xuyên di chuyển. C ++ có khả năng chạy vào hành vi không xác định mỗi khi bạn đưa ra tuyên bố.

  2. Rò rỉ bộ nhớ - Đây không phải là vấn đề đáng lo ngại đối với C ++ hiện đại, nhưng đối với người mới bắt đầu và trong khoảng một nửa thời gian tồn tại, C ++ khiến nó trở nên siêu dễ bị rò rỉ bộ nhớ. C ++ hiệu quả đã xuất hiện ngay xung quanh sự phát triển của thực tiễn để loại bỏ mối quan tâm này. Điều đó nói rằng, C # vẫn có thể rò rỉ bộ nhớ. Trường hợp phổ biến nhất mà mọi người gặp phải là chụp sự kiện. Nếu bạn có một đối tượng và đặt một trong các phương thức của nó làm xử lý cho một sự kiện, chủ sở hữu của sự kiện đó cần phải là đối tượng để đối tượng chết. Hầu hết những người mới bắt đầu không nhận ra rằng trình xử lý sự kiện được coi là một tài liệu tham khảo. Cũng có vấn đề với việc không xử lý các tài nguyên dùng một lần có thể rò rỉ bộ nhớ, nhưng những vấn đề này gần như không phổ biến như các con trỏ trong C ++ trước hiệu quả.

  3. Biên dịch - C ++ có mô hình biên dịch chậm. Điều này dẫn đến một số thủ thuật để chơi tốt với nó và giảm thời gian biên dịch.

  4. Chuỗi - Modern C ++ làm cho điều này tốt hơn một chút, nhưng char*chịu trách nhiệm cho ~ 95% tất cả các vi phạm bảo mật trước năm 2000. Đối với các lập trình viên có kinh nghiệm, họ sẽ tập trung vào std::string, nhưng đó vẫn là điều cần tránh và là vấn đề trong các thư viện cũ / tệ hơn . Và đó là cầu nguyện rằng bạn không cần hỗ trợ unicode.

Và thực sự, đó là đỉnh của tảng băng trôi. Vấn đề chính là C ++ là một ngôn ngữ rất kém cho người mới bắt đầu. Nó khá không nhất quán, và nhiều cạm bẫy thực sự, thực sự tồi tệ cũ đã được xử lý bằng cách thay đổi thành ngữ. Vấn đề là người mới bắt đầu sau đó cần phải học các thành ngữ từ một cái gì đó như C ++ hiệu quả. C # loại bỏ rất nhiều những vấn đề này hoàn toàn, và làm cho phần còn lại bớt lo lắng cho đến khi bạn tiến xa hơn trên con đường học tập.

Có những cạm bẫy mới, khác nhau trong C # mà một lập trình viên C # mới nên biết không? Nếu vậy, tại sao họ không thể tránh được thiết kế của C #?

Tôi đã đề cập đến vấn đề "rò rỉ bộ nhớ". Đây không phải là vấn đề ngôn ngữ nhiều như lập trình viên mong đợi thứ gì đó mà ngôn ngữ không thể làm được.

Một điều nữa là bộ hoàn thiện cho một đối tượng C # không được đảm bảo về mặt kỹ thuật để được chạy bởi bộ thực thi. Điều này thường không quan trọng, nhưng nó khiến một số thứ được thiết kế khác với những gì bạn mong đợi.

Một cạm bẫy nửa khác mà tôi thấy các lập trình viên gặp phải là ngữ nghĩa của các hàm ẩn danh. Khi bạn chụp một biến, bạn nắm bắt biến đó . Thí dụ:

List<Action> actions = new List<Action>();
for(int x = 0; x < 10; ++x ){
    actions.Add(() => Console.WriteLine(x));
}

foreach(var action in actions){
    action();
}

Không làm những gì ngây thơ được nghĩ. Cái này in 1010 lần.

Tôi chắc chắn có một số người khác tôi đang quên, nhưng vấn đề chính là họ ít phổ biến hơn.


4
Rò rỉ bộ nhớ là một điều của quá khứ, và cũng vậy char*. Chưa kể rằng bạn vẫn có thể rò rỉ bộ nhớ trong C # tốt.
DeadMG

2
Gọi mẫu "dán chuỗi vinh quang" là một chút nhiều. Mẫu thực sự là một trong những tính năng tốt nhất của C ++.
Charles Salvia

2
@CharlesSalvia Chắc chắn, họ là những tính năng thực sự phân biệt của C ++. Và vâng, đó có lẽ là một sự đơn giản hóa cho tác động biên dịch. Nhưng chúng có tác động không tương xứng đến thời gian biên dịch và kích thước đầu ra, đặc biệt nếu bạn không cẩn thận.
Telastyn

2
@deadMG chắc chắn, mặc dù tôi sẽ lập luận rằng nhiều thủ thuật lập trình meta mẫu được sử dụng / cần thiết trong C ++ được ... thực hiện tốt hơn thông qua một cơ chế khác.
Telastyn

2
@Telastyn toàn bộ quan điểm của type_traits là lấy thông tin loại tại thời gian biên dịch để bạn có thể sử dụng thông tin này để thực hiện những việc như chuyên môn mẫu hoặc chức năng quá tải theo cách cụ thể bằng cách sử dụngenable_if
Charles Salvia

10

Theo tôi, sự nguy hiểm của C ++ có phần cường điệu.

Mối nguy hiểm cơ bản là đây: Mặc dù C # cho phép bạn thực hiện các thao tác con trỏ "không an toàn" bằng unsafetừ khóa, C ++ (chủ yếu là siêu ký tự của C) sẽ cho phép bạn sử dụng con trỏ bất cứ khi nào bạn cảm thấy thích. Bên cạnh những nguy hiểm thông thường vốn có khi sử dụng con trỏ (giống với C), như rò rỉ bộ nhớ, tràn bộ đệm, con trỏ lơ lửng, v.v., C ++ giới thiệu những cách mới để bạn thực hiện mọi thứ một cách nghiêm túc.

"Sợi dây phụ" này, có thể nói, mà Joel Spolsky đang nói đến, về cơ bản nói đến một điều: viết các lớp học quản lý nội bộ của chính họ, còn được gọi là " Quy tắc 3 " (giờ đây có thể được gọi là Quy tắc của 4 hoặc Quy tắc 5 trong C ++ 11). Điều này có nghĩa là, nếu bạn từng muốn viết một lớp quản lý phân bổ bộ nhớ của riêng mình trong nội bộ, bạn phải biết bạn đang làm gì nếu không chương trình của bạn sẽ có khả năng bị sập. Bạn phải cẩn thận tạo một hàm tạo, sao chép hàm tạo, hàm hủy và toán tử gán, điều này rất dễ gây ra lỗi, thường dẫn đến các sự cố kỳ lạ khi chạy.

TUY NHIÊN , trong lập trình C ++ thực tế hàng ngày, thực sự rất hiếm khi viết một lớp quản lý bộ nhớ của chính nó, vì vậy thật sai lầm khi nói rằng các lập trình viên C ++ luôn cần phải "cẩn thận" để tránh những cạm bẫy này. Thông thường, bạn sẽ chỉ làm một cái gì đó giống như:

class Foo
{
    public:

    Foo(const std::string& s) 
        : m_first_name(s)
    { }

    private:

    std::string m_first_name;
};

Lớp này trông khá gần với những gì bạn làm trong Java hoặc C # - nó không yêu cầu quản lý bộ nhớ rõ ràng (vì lớp thư viện std::stringsẽ tự động xử lý tất cả những thứ đó) và không yêu cầu "Quy tắc 3" nào cả mặc định sao chép constructor và toán tử gán là tốt.

Chỉ khi bạn cố gắng làm một cái gì đó như:

class Foo
{
    public:

    Foo(const char* s)
    { 
        std::size_t len = std::strlen(s);
        m_name = new char[len + 1];
        std::strcpy(m_name, s);
    }

    Foo(const Foo& f); // must implement proper copy constructor

    Foo& operator = (const Foo& f); // must implement proper assignment operator

    ~Foo(); // must free resource in destructor

    private:

    char* m_name;
};

Trong trường hợp này, có thể khó khăn cho người mới để có được phép gán, hàm hủy và sao chép chính xác. Nhưng đối với hầu hết các trường hợp, không có lý do gì để làm điều này. C ++ giúp dễ dàng tránh việc quản lý bộ nhớ thủ công 99% thời gian bằng cách sử dụng các lớp thư viện như std::stringstd::vector.

Một vấn đề liên quan khác là quản lý bộ nhớ theo cách thủ công theo cách không tính đến khả năng ngoại lệ bị ném. Giống:

char* s = new char[100];
some_function_which_may_throw();
/* ... */
delete[] s;

Nếu some_function_which_may_throw()thực sự không có ngoại lệ, bạn sẽ bị rò rỉ bộ nhớ vì bộ nhớ được phân bổ ssẽ không bao giờ được lấy lại. Nhưng một lần nữa, trong thực tế, điều này hầu như không còn là vấn đề nữa vì cùng một lý do là "Quy tắc 3" thực sự không còn là vấn đề nữa. Rất hiếm (và thường là không cần thiết) để thực sự quản lý bộ nhớ của riêng bạn bằng các con trỏ thô. Để tránh vấn đề trên, tất cả những gì bạn cần làm là sử dụng một std::stringhoặc std::vector, và hàm hủy sẽ tự động được gọi trong khi giải nén ngăn xếp sau khi ném ngoại lệ.

Vì vậy, một chủ đề chung ở đây là nhiều tính năng C ++ không được kế thừa từ C, như khởi tạo / hủy tự động, xây dựng bản sao và ngoại lệ, buộc lập trình viên phải hết sức cẩn thận khi thực hiện quản lý bộ nhớ thủ công trong C ++. Nhưng một lần nữa, đây chỉ là vấn đề nếu bạn có ý định quản lý bộ nhớ thủ công ngay từ đầu, điều này hầu như không còn cần thiết nữa khi bạn có các thùng chứa tiêu chuẩn và con trỏ thông minh.

Vì vậy, theo tôi, trong khi C ++ cung cấp cho bạn rất nhiều dây, thì hầu như không cần thiết phải sử dụng nó để treo cổ và những cạm bẫy mà Joel đang nói đến là rất dễ tránh trong C ++ hiện đại.


Đó là quy tắc ba trong C ++ 03 và bây giờ là quy tắc bốn trong C ++ 11.
DeadMG

1
Bạn có thể gọi nó là "Quy tắc 5" cho sao chép hàm tạo, di chuyển hàm tạo, gán sao chép, gán di chuyển và hàm hủy. Nhưng ngữ nghĩa di chuyển không phải lúc nào cũng cần thiết chỉ để quản lý tài nguyên thích hợp.
Charles Salvia

Bạn không cần chuyển riêng và sao chép bài tập. Thành ngữ sao chép và hoán đổi có thể làm cả hai toán tử trong một.
DeadMG

2
Nó trả lời câu hỏi Does C# avoid pitfalls that are avoided in C++ only by careful programming?. Câu trả lời là "không thực sự, bởi vì thật dễ dàng để tránh những cạm bẫy mà Joel đã nói về C ++ hiện đại"
Charles Salvia

1
IMO, trong khi các ngôn ngữ cấp cao như C # hoặc Java cung cấp cho bạn quản lý bộ nhớ và các công cụ khác được cho là giúp bạn, thì không phải lúc nào cũng làm như được yêu cầu. Cuối cùng, bạn vẫn chăm sóc thiết kế mã của mình để không bị rò rỉ bộ nhớ (đó không chính xác là những gì bạn sẽ gọi trong C ++). Từ kinh nghiệm của tôi, tôi thấy việc quản lý bộ nhớ trong C ++ dễ dàng hơn vì bạn biết rằng các hàm hủy sẽ được gọi và trong hầu hết các trường hợp chúng sẽ dọn sạch. Xét cho cùng, C ++ có con trỏ thông minh cho các trường hợp khi thiết kế không cho phép quản lý bộ nhớ hiệu quả. C ++ là tuyệt vời nhưng không dành cho người giả.
Pijusn

3

Tôi sẽ không thực sự đồng ý. Có lẽ ít cạm bẫy hơn C ++ như nó đã tồn tại vào năm 1985.

Có phải C # tránh những cạm bẫy được tránh trong C ++ chỉ bằng cách lập trình cẩn thận? Nếu vậy, ở mức độ nào và làm thế nào họ tránh được?

Không hẳn vậy. Các quy tắc như Quy tắc ba đã mất đi ý nghĩa to lớn trong C ++ 11 nhờ unique_ptrshared_ptrđược Chuẩn hóa. Sử dụng các lớp Tiêu chuẩn theo cách mơ hồ không phải là "mã hóa cẩn thận", đó là "mã hóa cơ bản". Thêm vào đó, tỷ lệ dân số C ++ vẫn còn đủ ngu ngốc, không hiểu biết hoặc cả hai để làm những việc như quản lý bộ nhớ thủ công thấp hơn rất nhiều so với trước đây. Thực tế là các giảng viên muốn thể hiện các quy tắc như vậy phải mất hàng tuần cố gắng tìm các ví dụ mà họ vẫn áp dụng, bởi vì các lớp Tiêu chuẩn bao gồm hầu như mọi trường hợp sử dụng có thể tưởng tượng được. Nhiều kỹ thuật C ++ hiệu quả đã đi theo cách tương tự - cách của dodo. Nhiều người khác không thực sự là C ++ cụ thể. Hãy để tôi xem. Bỏ qua mục đầu tiên, mười mục tiếp theo là:

  1. Đừng viết mã C ++ như nó C. Đây thực sự chỉ là lẽ thường.
  2. Hạn chế giao diện của bạn và sử dụng đóng gói. OOP.
  3. Người viết mã khởi tạo hai pha nên được đốt tại cọc. OOP.
  4. Biết giá trị ngữ nghĩa là gì. Đây thực sự là C ++ cụ thể?
  5. Hạn chế giao diện của bạn một lần nữa, lần này theo một cách hơi khác. OOP.
  6. Kẻ hủy diệt ảo. Vâng. Điều này có lẽ vẫn còn hiệu lực - phần nào. finaloverrideđã giúp thay đổi trò chơi đặc biệt này tốt hơn. Tạo hàm hủy của bạn overridevà bạn đảm bảo một lỗi trình biên dịch đẹp nếu bạn thừa hưởng từ một người không tạo ra hàm hủy của chúng virtual. Làm cho lớp của bạn finalvà không có chà kém có thể đi cùng và vô tình thừa hưởng từ nó mà không có một công cụ phá hủy ảo.
  7. Điều tồi tệ xảy ra nếu chức năng dọn dẹp thất bại. Điều này không thực sự cụ thể đối với C ++ - bạn có thể thấy cùng một lời khuyên cho cả Java và C # - và, tốt, khá nhiều ngôn ngữ. Có các chức năng dọn dẹp có thể thất bại chỉ là xấu và không có gì C ++ hoặc thậm chí OOP về mặt hàng này.
  8. Hãy nhận biết làm thế nào để thứ tự xây dựng ảnh hưởng đến các chức năng ảo. Thật vui, trong Java (hiện tại hoặc quá khứ), nó chỉ đơn giản gọi không đúng chức năng của lớp Derogen, thậm chí còn tệ hơn cả hành vi của C ++. Bất kể, vấn đề này không cụ thể đối với C ++.
  9. Quá tải nhà điều hành nên hành xử như mọi người mong đợi. Không thực sự cụ thể. Chết tiệt, hầu như không quá tải toán tử cụ thể, điều tương tự có thể được áp dụng cho bất kỳ chức năng nào - đừng đặt cho nó một tên và sau đó làm cho nó làm một cái gì đó hoàn toàn không trực quan.
  10. Điều này thực sự được coi là thực hành xấu. Tất cả các toán tử gán gán an toàn ngoại lệ đều xử lý tốt việc tự gán và việc tự gán thực sự là một lỗi chương trình hợp lý và việc kiểm tra tự gán chỉ không đáng với chi phí hiệu năng.

Rõ ràng là tôi sẽ không xem xét từng mục C ++ hiệu quả, nhưng hầu hết chúng chỉ đơn giản là áp dụng các khái niệm cơ bản cho C ++. Bạn sẽ tìm thấy lời khuyên tương tự trong bất kỳ ngôn ngữ toán tử quá tải định hướng đối tượng được gõ giá trị nào. Các hàm hủy ảo là về cái duy nhất là một cạm bẫy của C ++ và vẫn còn hiệu lực - mặc dù, có thể nói, với finallớp C ++ 11, nó không hợp lệ như nó. Hãy nhớ rằng C ++ hiệu quả đã được viết khi ý tưởng áp dụng OOP và các tính năng cụ thể của C ++ vẫn còn rất mới. Các mục này hầu như không phải là cạm bẫy của C ++ và nhiều hơn về cách đối phó với sự thay đổi từ C và cách sử dụng OOP một cách chính xác.

Chỉnh sửa: Cạm bẫy của C ++ không bao gồm những thứ như cạm bẫy của malloc. Ý tôi là, đối với một, mọi cạm bẫy mà bạn có thể tìm thấy trong mã C mà bạn có thể tìm thấy bằng mã C # không an toàn, do đó, điều đó không đặc biệt liên quan, và thứ hai, chỉ vì Tiêu chuẩn xác định nó để tương tác không có nghĩa là sử dụng nó được coi là C ++ mã. Tiêu chuẩn cũng xác định goto, nhưng nếu bạn định viết một đống mỳ spaghetti khổng lồ bằng cách sử dụng nó, tôi sẽ coi đó là vấn đề của bạn chứ không phải ngôn ngữ. Có một sự khác biệt lớn giữa "mã hóa cẩn thận" và "Theo các thành ngữ cơ bản của ngôn ngữ".

Có những cạm bẫy mới, khác nhau trong C # mà một lập trình viên C # mới nên biết không? Nếu vậy, tại sao họ không thể tránh được thiết kế của C #?

usinghút. Nó thực sự làm. Và tôi không biết tại sao điều gì đó tốt hơn lại không được thực hiện. Ngoài ra, Base[] = Derived[]và hầu như mọi việc sử dụng Object, tồn tại bởi vì các nhà thiết kế ban đầu đã không nhận thấy thành công to lớn mà các mẫu có trong C ++ và quyết định rằng "Chúng ta hãy thừa hưởng mọi thứ từ mọi thứ và mất đi sự an toàn của chúng ta" là sự lựa chọn thông minh hơn . Tôi cũng tin rằng bạn có thể tìm thấy một số bất ngờ khó chịu trong những điều như điều kiện cuộc đua với các đại biểu và những niềm vui khác. Sau đó, có những thứ chung chung khác, như cách các thế hệ hút kinh khủng so với các mẫu, việc thực thi thực sự không cần thiết thực sự đặt mọi thứ trong một class, và những thứ như vậy.


5
Một cơ sở người dùng có giáo dục hoặc các cấu trúc mới không thực sự làm giảm sợi dây. Họ chỉ là những người làm việc để mọi người cuối cùng bị treo. Mặc dù đây là tất cả các bình luận tốt về C ++ hiệu quả và bối cảnh của nó trong quá trình phát triển của ngôn ngữ.
Telastyn

2
Không. Đó là về cách một loạt các mục trong C ++ hiệu quả là các khái niệm có thể áp dụng như nhau cho bất kỳ ngôn ngữ hướng đối tượng được nhập giá trị nào. Và việc giáo dục cơ sở người dùng để mã hóa C ++ thực tế thay vì C chắc chắn đang làm giảm đi sợi dây mà C ++ mang lại cho bạn. Ngoài ra, tôi hy vọng rằng các cấu trúc ngôn ngữ mới đang làm giảm dần sợi dây. Đó là về cách chỉ vì Tiêu chuẩn C ++ xác định mallockhông có nghĩa là bạn nên làm điều đó, bất kể chỉ vì bạn có thể làm con điếm gotonhư chó cái có nghĩa là đó là sợi dây bạn có thể tự treo.
DeadMG

2
Sử dụng các phần C của C ++ không khác gì viết tất cả mã của bạn unsafebằng C #, điều này cũng tệ như vậy. Tôi cũng có thể liệt kê mọi cạm bẫy của mã hóa C # như C, nếu bạn muốn.
DeadMG

@DeadMG: vậy thực sự câu hỏi phải là "một lập trình viên C ++ có đủ dây để tự treo mình miễn là anh ta là lập trình viên C"
gbjbaanb

"Thêm vào đó, tỷ lệ dân số C ++ vẫn còn đủ ngu ngốc, không hiểu biết hoặc cả hai để làm những việc như quản lý bộ nhớ thủ công thấp hơn rất nhiều so với trước đây." Cần dẫn nguồn.
dan04

3

Có phải C # tránh những cạm bẫy được tránh trong C ++ chỉ bằng cách lập trình cẩn thận? Nếu vậy, ở mức độ nào và làm thế nào họ tránh được?

C # có những ưu điểm của:

  • Không tương thích ngược với C, do đó tránh có một danh sách dài các tính năng ngôn ngữ "xấu" (ví dụ: con trỏ thô) về mặt cú pháp thuận tiện nhưng hiện được coi là phong cách xấu.
  • Có ngữ nghĩa tham chiếu thay vì ngữ nghĩa giá trị, điều này tạo ra ít nhất 10 trong số các mục C ++ hiệu quả (nhưng đưa ra những cạm bẫy mới).
  • Có ít hành vi được xác định thực hiện hơn C ++.
    • Đặc biệt, trong C ++ mã hóa các ký tự của char, stringvv là thực hiện xác định. Sự phân ly giữa cách tiếp cận Windows với Unicode ( wchar_tđối với UTF-16, charđối với "trang mã" lỗi thời) và cách tiếp cận * nix (UTF-8) gây ra những khó khăn lớn trong mã đa nền tảng. C #, OTOH, đảm bảo rằng a stringlà UTF-16.

Có những cạm bẫy mới, khác nhau trong C # mà một lập trình viên C # mới nên biết không?

Đúng: IDisposable

Có một cuốn sách tương đương với "C ++ hiệu quả" cho C # không?

Có một cuốn sách tên là C # hiệu quả có cấu trúc tương tự như C ++ hiệu quả .


0

Không, C # (và Java) kém an toàn hơn C ++

C ++ có thể kiểm chứng tại địa phương . Tôi có thể kiểm tra một lớp duy nhất trong C ++ và xác định rằng lớp đó không bị rò rỉ bộ nhớ hoặc các tài nguyên khác, giả sử rằng tất cả các lớp được tham chiếu là chính xác. Trong Java hoặc C #, cần kiểm tra mọi lớp được tham chiếu để xác định xem nó có yêu cầu hoàn thiện một số loại không.

C ++:

{
   some_resource r(...);  // resource initialized
   ...
}  // resource destructor called, no leaks here

C #:

{
   SomeResource r = new SomeResource(...); // resource initialized
   ...
} // did I need to finalize that?  May I should have used 'using' 
  // (or in Java, a grotesque try/finally construct)?  No way to tell
  // without checking the documentation for SomeResource

C ++:

{
    auto_ptr<SomeInterface> i = SomeFactory.create(...);
    i->f(...);
} // automatic finalization and memory release.  A new implementation of
  // SomeInterface can allocate and free resources with no impact
  // on existing code

C #:

{
   SomeInterface i = SomeFactory.create(...);
   i.f(...);
   ...
} // Sure hope someone didn't create an implementation of SomeInterface
  // that requires finalization.  In C# and Java it is necessary to decide whether
  // any implementation could require finalization when the interface is defined.
  // If the initial decision is 'no finalization', then no future implementation  
  // can acquire any resource without creating potential leaks in existing code.

3
... Thật là tầm thường trong các IDE hiện đại để xác định xem có thứ gì đó thừa hưởng từ IDis Dùng hay không. Vấn đề chính là bạn cần biết để sử dụng auto_ptr(hoặc một vài người thân của nó). Đó là sợi dây tục ngữ.
Telastyn

2
@Telastyn không, vấn đề là bạn luôn sử dụng một con trỏ thông minh, trừ khi bạn thực sự biết rằng bạn không cần nó. Trong C #, câu lệnh sử dụng giống như sợi dây bạn đang đề cập đến. (tức là trong C ++, bạn phải nhớ sử dụng một con trỏ thông minh, tại sao C # không tệ như vậy mặc dù bạn phải nhớ luôn sử dụng câu lệnh sử dụng)
gbjbaanb

1
@gbjbaanb Vì cái gì? 5% tại hầu hết các lớp C # là dùng một lần? Và bạn biết rằng bạn cần phải vứt bỏ chúng nếu chúng dùng một lần. Trong C ++, mọi đối tượng đều dùng một lần. Và bạn không biết nếu trường hợp cụ thể của bạn cần phải được xử lý. Điều gì xảy ra cho con trỏ được trả lại mà không phải từ một nhà máy? Bạn có trách nhiệm dọn dẹp chúng không? Nó không nên, nhưng đôi khi nó là. Và một lần nữa, chỉ vì bạn nên luôn luôn sử dụng một con trỏ thông minh không có nghĩa là tùy chọn không ngừng tồn tại. Đối với người mới bắt đầu đặc biệt, đây là một cạm bẫy đáng kể.
Telastyn

2
@Telastyn: Biết sử dụng auto_ptrcũng đơn giản như biết sử dụng IEnumerablehoặc biết sử dụng giao diện, hoặc không sử dụng dấu phẩy động cho tiền tệ hoặc đại loại như vậy. Đây là một ứng dụng cơ bản của DRY. Không ai biết những điều cơ bản về cách lập trình sẽ phạm sai lầm đó. Không giống như using. Vấn đề với usinglà bạn phải biết cho mọi lớp xem nó có dùng một lần hay không (và tôi hy vọng rằng sẽ không bao giờ thay đổi) và nếu không phải là Dùng một lần, bạn sẽ tự động cấm tất cả các lớp dẫn xuất có thể phải dùng một lần.
DeadMG

2
kevin: Uh, câu trả lời của bạn không có ý nghĩa. Đó không phải là lỗi của C # mà bạn đang làm sai. Bạn làm KHÔNG phụ thuộc vào finalizers bằng văn bản đúng mã C # . Nếu bạn có một trường có một Disposephương thức, bạn phải thực hiện IDisposable(cách 'phù hợp'). Nếu lớp của bạn làm điều đó (tương đương với việc triển khai RAII cho lớp của bạn trong C ++) và bạn sử dụng using(giống như các con trỏ thông minh trong C ++), thì tất cả đều hoạt động hoàn hảo. Công cụ hoàn thiện chủ yếu là để ngăn ngừa tai nạn - Disposechịu trách nhiệm về tính chính xác và nếu bạn không sử dụng nó, đó là lỗi của bạn, không phải của C #.
dùng541686

0

Có 100% có vì tôi nghĩ không thể giải phóng bộ nhớ và sử dụng nó trong C # (giả sử nó được quản lý và bạn không chuyển sang chế độ không an toàn).

Nhưng nếu bạn biết cách lập trình trong C ++ mà một số lượng người không thể tin được. Bạn khá ổn Giống như các lớp Charles Salvia không thực sự quản lý ký ức của họ vì tất cả đều được xử lý trong các lớp STL có từ trước. Tôi hiếm khi sử dụng con trỏ. Trong thực tế, tôi đã đi các dự án mà không sử dụng một con trỏ. (C ++ 11 làm cho điều này dễ dàng hơn).

Đối với việc mắc lỗi chính tả, những lỗi ngớ ngẩn và v.v (ví dụ: if (i=0)bc khóa bị kẹt khi bạn nhấn == thực sự nhanh chóng) trình biên dịch phàn nàn là tốt vì nó cải thiện chất lượng mã. Ví dụ khác là quên breaktrong các câu lệnh chuyển đổi và không cho phép bạn khai báo các biến tĩnh trong một hàm (đôi khi tôi không thích nhưng đó là một ý tưởng tốt imo).


4
Java và C # làm cho vấn đề =/ ==thậm chí còn tồi tệ hơn bằng cách sử dụng ==cho đẳng thức tham chiếu và giới thiệu .equalscho đẳng thức giá trị. Lập trình viên nghèo bây giờ phải theo dõi xem một biến là 'nhân đôi' hay 'Nhân đôi' và chắc chắn gọi đúng biến thể.
kevin cline

@kevincline +1 nhưng trong C #, structbạn có thể thực hiện ==công việc cực kỳ tốt vì phần lớn thời gian người ta chỉ có chuỗi, ints và float (tức là chỉ các thành viên cấu trúc). Trong mã riêng của tôi, tôi không bao giờ gặp phải vấn đề đó trừ khi tôi muốn so sánh các mảng. Tôi không nghĩ mình từng so sánh danh sách hoặc các loại không có cấu trúc (chuỗi, int, float, DateTime, KeyValuePair và nhiều loại khác)

2
Python đã làm đúng bằng cách sử dụng ==cho đẳng thức giá trị và ischo đẳng thức tham chiếu.
dan04

@ dan04 - Bạn nghĩ C # có bao nhiêu loại bình đẳng? Xem cuộc trò chuyện chớp nhoáng tuyệt vời của ACCU: Một số đối tượng bình đẳng hơn những đối tượng khác
Mark booth
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.