Tôi tin rằng tôi đã trộn mã C và C ++ khi không nên có; Đây có phải là một vấn đề và làm thế nào để khắc phục?


10

Bối cảnh / Kịch bản

Tôi bắt đầu viết một ứng dụng CLI hoàn toàn bằng C (chương trình C hoặc C ++ thích hợp đầu tiên của tôi không phải là "Hello World" hoặc một biến thể của chúng). Khoảng giữa chừng tôi đã làm việc với "chuỗi" đầu vào của người dùng (mảng char) và tôi phát hiện ra đối tượng truyền phát chuỗi C ++. Tôi thấy rằng tôi có thể lưu mã bằng cách sử dụng chúng, vì vậy tôi đã sử dụng chúng thông qua ứng dụng. Điều này có nghĩa là tôi đã thay đổi phần mở rộng tệp thành .cpp và bây giờ biên dịch ứng dụng g++thay vì gcc. Vì vậy, dựa trên điều này, tôi sẽ nói rằng ứng dụng này về mặt kỹ thuật là một ứng dụng C ++ (mặc dù 90% + mã được viết bằng cái mà tôi sẽ gọi là C, vì có rất nhiều giao thoa giữa hai ngôn ngữ với kinh nghiệm hạn chế của tôi về cả hai). Nó là một tệp .cpp duy nhất dài khoảng 900 dòng.

Yếu tố quan trọng

Tôi muốn chương trình này miễn phí (như tiền) và tự do phân phối và có thể sử dụng cho tất cả mọi người có. Mối quan tâm của tôi là ai đó sẽ xem mã và nghĩ gì đó về tác dụng của:

Hãy nhìn vào mã hóa, thật kinh khủng, chương trình này không thể giúp tôi

Khi có khả năng nó có thể! Một vấn đề khác là mã có hiệu quả (nó là một chương trình để kiểm tra kết nối Ethernet). Không nên có phần mã nào kém hiệu quả đến mức chúng có thể cản trở nghiêm trọng hiệu năng của ứng dụng hoặc đầu ra của nó. Tuy nhiên, tôi nghĩ đó là một câu hỏi cho Stack Overflow khi yêu cầu trợ giúp với các chức năng, phương thức, cuộc gọi đối tượng cụ thể, v.v.

Câu hỏi của tôi

Có (theo ý kiến ​​của tôi) đã trộn lẫn C và C ++, có lẽ tôi không nên. Tôi có nên tìm cách viết lại tất cả trong C ++ không mang tất cả "trở lại" vào mã C? Có một cách tiếp cận đúng ở đây? Tôi bị lạc và cần một số hướng dẫn về cách giữ cho ứng dụng này "Tốt" trong mắt số đông, vì vậy họ sẽ sử dụng nó và hưởng lợi từ nó.

Mã - Cập nhật

Đây là một liên kết đến mã. Đó là khoảng 40% ý kiến, tôi bình luận gần như mọi dòng cho đến khi tôi cảm thấy trôi chảy hơn. Trong bản sao tôi đã liên kết đến, tôi đã loại bỏ khá nhiều ý kiến. Tôi hy vọng điều này không làm cho nó quá khó để đọc. Tôi hy vọng rằng không ai cần phải hiểu đầy đủ về nó. Nếu tôi đã tạo ra các lỗi thiết kế nghiêm trọng, tôi hy vọng chúng có thể được nhận dạng dễ dàng. Tôi cũng nên đề cập, tôi đang viết một vài máy tính để bàn và máy tính xách tay Ubuntu. Tôi không có ý định chuyển mã sang các hệ điều hành khác.


3
Tôi định hướng CLI cho bạn. CLI cũng có thể đề cập đến Cơ sở hạ tầng ngôn ngữ chung, không có ý nghĩa nhiều từ góc độ C.
Robert Harvey

Bạn có thể viết lại nó trong FORTRAN. Tôi chưa bao giờ nghe về OOF.
ott--

2
Tôi sẽ không lo lắng quá nhiều về việc mọi người không sử dụng phần mềm của bạn nếu nó không "đẹp". 99% người dùng của bạn thậm chí sẽ không nhìn vào mã hoặc quan tâm đến việc nó được viết như thế nào miễn là nó hoàn thành những gì họ cần. Tuy nhiên, điều quan trọng là mã phải nhất quán, vv để giúp bạn duy trì lâu dài.
Evicatos

1
Tại sao bạn không tạo ra phần mềm miễn phí (ví dụ: theo giấy phép GPLv3 ), với mã trên ví dụ như github . Mã của bạn đang thiếu một LICENSEtập tin. Bạn có thể nhận được phản hồi thú vị.
Basile Starynkevitch

Câu trả lời:


13

Hãy bắt đầu từ đầu: mã C và C ++ hỗn hợp khá phổ biến. Vì vậy, bạn đang ở trong một câu lạc bộ lớn để bắt đầu. Chúng ta có các cơ sở mã C khổng lồ trong tự nhiên. Nhưng vì những lý do rõ ràng, nhiều lập trình viên từ chối viết ít nhất những thứ mới trong C, có quyền truy cập vào C ++ trong cùng một trình biên dịch, các mô-đun mới bắt đầu được viết theo cách đó - lúc đầu chỉ để lại các phần hiện có.

Sau đó, một số tệp hiện có được biên dịch lại thành C ++ và một số cầu có thể bị xóa ... Nhưng nó có thể mất nhiều thời gian.

Bạn đang đi trước một chút, toàn bộ hệ thống của bạn bây giờ là C ++, hầu hết được viết là "C-style". Và bạn thấy sự pha trộn của các phong cách là một vấn đề, điều bạn không nên: C ++ là một ngôn ngữ đa mô hình hỗ trợ nhiều phong cách và cho phép chúng cùng tồn tại mãi mãi. Trên thực tế đó là sức mạnh chính, mà bạn không bị ép buộc theo một phong cách duy nhất. Một thứ sẽ là tối ưu ở đây và đó, với một số may mắn không phải ở đâu cũng có.

Làm việc lại codebase là một ý tưởng tốt, NẾU nó bị hỏng. Hoặc nếu nó là trong cách phát triển. Nhưng nếu nó hoạt động (theo nghĩa gốc của từ), vui lòng tuân theo nguyên tắc kỹ thuật cơ bản nhất: nếu nó không bị hỏng, đừng sửa nó. Để các bộ phận lạnh một mình, đặt nỗ lực của bạn ở nơi nó được tính. Trên các bộ phận xấu, nguy hiểm - hoặc trong các tính năng mới, và chỉ cần cấu trúc lại các bộ phận để biến chúng thành một chiếc giường.

Nếu bạn tìm kiếm những điều chung chung để giải quyết, đây là những gì đáng để đuổi khỏi một cơ sở mã C:

  • tất cả các hàm str * và char [] - thay thế chúng bằng một lớp chuỗi
  • nếu bạn sử dụng sprintf, hãy tạo một phiên bản trả về một chuỗi với kết quả hoặc đặt nó trong chuỗi và thay thế việc sử dụng. (Nếu bạn không bao giờ bận tâm với các luồng, hãy tự mình bỏ qua chúng, trừ khi bạn thích chúng; gcc cung cấp loại an toàn hoàn hảo ngoài hộp để kiểm tra các định dạng, chỉ cần thêm thuộc tính thích hợp.
  • hầu hết malloc và miễn phí - KHÔNG phải với mới và xóa, mà là vector, danh sách, bản đồ và các tập hợp khác.
  • phần còn lại của quản lý bộ nhớ (sau hai điểm trước đó, nó phải khá hiếm, bao gồm các con trỏ thông minh hoặc thực hiện các bộ sưu tập đặc biệt của bạn
  • thay thế tất cả việc sử dụng tài nguyên khác (FILE *, mutex, lock, v.v.) để sử dụng các trình bao bọc hoặc lớp của RAII

Khi bạn hoàn thành việc bạn tiếp cận điểm mà codebase có thể an toàn ngoại lệ một cách hợp lý, do đó bạn có thể loại bỏ bóng đá mã trả về bằng cách sử dụng ngoại lệ và thử / bắt hiếm trong các chức năng cấp cao.

Ngoài ra, chỉ cần viết mã mới trong một số C ++ lành mạnh và nếu một số lớp được sinh ra là sự thay thế tốt trong mã hiện có, hãy chọn chúng.

Tôi đã không đề cập đến những thứ liên quan đến cú pháp, rõ ràng sử dụng ref thay vì con trỏ trong tất cả mã mới, nhưng thay thế các phần C cũ chỉ để thay đổi đó không có giá trị tốt. Các diễn viên bạn phải giải quyết, loại bỏ tất cả những gì bạn có thể và sử dụng các biến thể C ++ trong các hàm bao bọc cho phần còn lại. Và rất quan trọng, thêm const bất cứ nơi nào áp dụng. Những xen kẽ với những viên đạn trước đó. Và hợp nhất các macro của bạn và thay thế những gì bạn có thể tạo thành enum, hàm nội tuyến hoặc mẫu.

Tôi khuyên bạn nên đọc Tiêu chuẩn mã hóa C ++ của Sutter / Alexandrescu nếu chưa được thực hiện và theo sát chúng.


Cảm ơn rất nhiều về Balog đầu vào, tất cả lời khuyên âm thanh trong mắt tôi và tôi đồng ý với tất cả. Tôi sẽ xem xét để từ từ thay đổi phần mã theo phần tôi nghĩ, ưu tiên mã làm việc.
jwbensley

7

Nói tóm lại: bạn sẽ không xuống địa ngục, sẽ không có điều gì xấu xảy ra, nhưng bạn cũng sẽ không chiến thắng bất kỳ cuộc thi sắc đẹp nào.

Mặc dù hoàn toàn có thể sử dụng C ++ như một "C tốt hơn", bạn đang vứt bỏ nhiều lợi ích của C ++, nhưng vì bạn không giới hạn mình với vanilla C, nên bạn không nhận được bất kỳ lợi ích nào của C (tính đơn giản, tính di động , minh bạch, khả năng tương tác) hoặc. Nói cách khác: C ++ hy sinh một số phẩm chất của C để có được những thứ khác - ví dụ, tính minh bạch của C nơi bạn luôn có thể thấy rõ việc phân bổ bộ nhớ xảy ra ở đâu và khi nào được giao dịch cho sự trừu tượng mạnh mẽ hơn của C ++.

Vì mã của bạn dường như hoạt động tốt ngay bây giờ, có lẽ không nên viết lại nó chỉ vì lợi ích của nó: giữ nguyên như hiện tại và thay đổi nó thành C ++ thành ngữ hơn khi bạn đi, từng mảnh một, Bất cứ khi nào bạn làm việc trên bất kỳ phần nhất định. Và hãy ghi nhớ những bài học đã học trong dự án tiếp theo của bạn, hầu hết trong số này: C và C ++ không phải là cùng một ngôn ngữ, và tốt hơn hết là bạn nên đưa ra lựa chọn trước hơn là quyết định chuyển sang C ++ giữa chừng dự án của bạn .


Cảm ơn tdammers cho lời khuyên. Tôi đồng ý với những gì bạn đang nói và tôi đang mang nó lên máy bay, cảm ơn!
jwbensley

4

C ++ là một ngôn ngữ rất phức tạp, theo nghĩa là nó có rất nhiều tính năng. Nó cũng là một ngôn ngữ hỗ trợ nhiều mô hình, có nghĩa là nó cho phép bạn viết mã thủ tục hoàn toàn mà không cần sử dụng bất kỳ đối tượng nào.

Vì vậy, vì C ++ rất phức tạp, bạn thường thấy mọi người sử dụng một số tập hợp con giới hạn các tính năng của nó trong mã của họ. Người mới bắt đầu thường chỉ sử dụng luồng I / O, các đối tượng chuỗi và mới / xóa thay vì malloc / free và có thể tham chiếu thay vì con trỏ. Khi bạn tìm hiểu về các tính năng hướng đối tượng, bạn có thể bắt đầu viết theo kiểu gọi là "C với các lớp". Cuối cùng, khi bạn tìm hiểu thêm về C ++, bạn bắt đầu sử dụng các mẫu, RAII , STL , con trỏ thông minh, v.v.

Điểm tôi đang cố gắng thực hiện là học C ++ là một quá trình cần có thời gian. Vâng, ngay bây giờ mã của bạn có thể trông giống như được viết bởi một lập trình viên C đang cố gắng viết C ++. Và vì bạn chỉ đang học, điều đó hoàn toàn ổn. Tuy nhiên, nó làm tôi chùn bước, khi tôi thấy loại điều này trong mã sản xuất được viết bởi các lập trình viên dày dạn, những người nên biết rõ hơn.

Chỉ cần nhớ, một lập trình viên Fortran giỏi có thể viết mã Fortran tốt bằng bất kỳ ngôn ngữ nào. :)


2

Có vẻ như bạn đang tóm tắt lại chính xác con đường mà rất nhiều lập trình viên C cũ đã đi khi đến C ++. Bạn đang sử dụng nó như là một "C tốt hơn". Hai mươi năm trước điều này có ý nghĩa gì đó, nhưng tại thời điểm này có rất nhiều cấu trúc mạnh mẽ hơn trong C ++ (như Thư viện Mẫu Tiêu chuẩn) mà bây giờ hiếm khi có ý nghĩa. Tối thiểu, bạn có thể nên làm như sau để tránh cho các lập trình viên C ++ phình mạch khi xem mã của bạn:

  • Sử dụng luồng thay vì? printfgia đình.
  • Sử dụng newthay vì malloc.
  • Sử dụng các lớp container STL cho tất cả các cấu trúc dữ liệu.
  • Sử dụng std::stringthay vì char*bất cứ nơi nào có thể
  • Hiểu và sử dụng RAII

Nếu chương trình của bạn ngắn (và ~ 900 dòng có vẻ ngắn) Cá nhân tôi không nghĩ việc cố gắng tạo ra một tập hợp các lớp mạnh mẽ là bắt buộc hoặc thậm chí hữu ích.


Cảm ơn lời khuyên của Steven, Có, tôi có cùng ý tưởng về các lớp học và tôi nghĩ rằng tôi có thể sắp xếp mã thành một tệp gọn gàng. Cảm ơn!
jwbensley

2

Câu trả lời được chấp nhận chỉ đề cập đến ưu điểm của việc chuyển đổi C thành C ++ thành ngữ, như thể mã C ++ sẽ có ý nghĩa tuyệt đối tốt hơn mã C. Tôi đồng ý với các câu trả lời khác rằng có lẽ không cần thiết phải thực hiện bất kỳ thay đổi mạnh mẽ nào đối với mã hỗn hợp nếu nó không bị hỏng.

Việc trộn C và C ++ có bền vững hay không phụ thuộc vào cách trộn. Các lỗi tinh vi có thể được đưa ra ví dụ khi các ngoại lệ được nêu ra trong mã C (không ngoại lệ an toàn), gây rò rỉ bộ nhớ hoặc hỏng dữ liệu. Cách an toàn hơn và khá phổ biến là bọc các thư viện hoặc giao diện C trong các lớp hoặc sử dụng chúng trong một số loại cách ly khác trong dự án C ++.

Trước khi bạn quyết định viết lại toàn bộ dự án của bạn thành C ++ (hoặc C) thành ngữ, bạn nên lưu ý rằng nhiều thay đổi được trình bày trong các câu trả lời khác có thể làm cho chương trình của bạn chậm hơn hoặc gây ra các hiệu ứng không mong muốn khác. Ví dụ:

  • thay đổi chuỗi C được phân bổ ngăn xếp thành chuỗi std :: có thể gây ra phân bổ heap không cần thiết
  • thay đổi con trỏ thô thành một số loại con trỏ dùng chung (như std :: shared_ptr) gây ra chi phí truy cập do tính tham chiếu, chức năng thành viên ảo nội bộ và an toàn luồng
  • luồng thư viện chuẩn chậm hơn so với đối tác C
  • Việc sử dụng bất cẩn các lớp RAII, như các thùng chứa, có thể gây ra các hoạt động không cần thiết, đặc biệt là khi không thể sử dụng ngữ nghĩa của C ++ 11
  • các mẫu có thể gây ra thời gian biên dịch dài hơn, lỗi biên dịch không rõ ràng, sự phình mã và các vấn đề về tính di động giữa các trình biên dịch
  • viết mã an toàn ngoại lệ là khó khăn, điều này làm cho việc giới thiệu các lỗi tinh vi trong quá trình chuyển đổi trở nên dễ dàng
  • Sẽ khó hơn khi sử dụng mã C ++ từ thư viện dùng chung so với C đơn giản
  • C ++ không được hỗ trợ rộng rãi như C89

Để kết luận, việc chuyển đổi từ mã C và C ++ hỗn hợp sang C ++ thành ngữ nên được xem như một sự đánh đổi có nhiều thứ hơn là chỉ có thời gian thực hiện và mở rộng hộp công cụ của bạn với các tính năng có vẻ thuận tiện. Do đó, rất khó để đưa ra câu trả lời cho trường hợp chung ngoài "nó phụ thuộc".


"Luồng thư viện chuẩn chậm hơn so với đối tác C": Có khả năng, chắc chắn khó khăn hơn cho việc quốc tế hóa so với printf.
Ded repeatator

1

Đó là hình thức xấu để trộn C và C ++. Chúng là những ngôn ngữ riêng biệt và thực sự nên được đối xử như vậy. Chọn bất cứ thứ gì bạn cảm thấy thoải mái nhất và cố gắng viết mã thành ngữ bằng ngôn ngữ đó.

Nếu C ++ tiết kiệm cho bạn rất nhiều mã, thì hãy gắn bó với C ++ và viết lại các phần C. Tôi nghi ngờ một sự khác biệt hiệu suất thậm chí sẽ đáng chú ý, nếu đó là những gì bạn quan tâm.


Vì vậy, tôi có một thư viện mà tôi muốn sử dụng được viết bằng C và tôi có thư viện khác mà tôi muốn sử dụng được viết bằng C ++ ... Viết lại mã hoạt động tốt chỉ là tạo ra công việc không cần thiết.
gnasher729

1

Tôi luôn luôn đi lại giữa C và C ++ và là loại người kỳ quặc thích làm việc theo cách này. Tôi thích C ++ hơn cho những thứ cấp cao hơn trong khi tôi sử dụng C như một công cụ cùn cho phép tôi sử dụng các loại dữ liệu x-quang và coi chúng như các bit và byte với, memcpytôi thấy tiện lợi khi thực hiện các cấu trúc dữ liệu cấp thấp và cấp phát bộ nhớ . Nếu bạn thấy mình làm việc ở mức bit và byte thô, thì hệ thống loại thực sự phong phú của C ++ không giúp được gì cả, và tôi thường thấy việc viết mã như vậy trong C dễ dàng hơn khi tôi có thể giả định rằng tôi có thể an toàn rằng tôi có thể coi bất kỳ kiểu dữ liệu C nào chỉ là bit và byte.

Điều chính cần ghi nhớ là nơi hai ngôn ngữ này không kết nối tốt.

1. Hàm AC không bao giờ nên gọi hàm C ++ có thể throw. Cho biết có bao nhiêu loại mã C ++ hàng ngày của bạn có thể hoàn toàn chạy vào một ngoại lệ, điều đó thường có nghĩa là các hàm C của bạn không nên gọi các hàm C ++ nói chung. Nếu không, mã C của bạn sẽ không thể giải phóng tài nguyên mà nó phân bổ trong quá trình thư giãn ngăn xếp vì nó không có cách nào thực tế để bắt ngoại lệ C ++. Nếu cuối cùng bạn yêu cầu mã C của mình gọi hàm C ++ là gọi lại, ví dụ, thì phía C ++ phải đảm bảo rằng bất kỳ ngoại lệ nào nó gặp phải đều bị bắt trước khi quay lại mã C.

2. Nếu bạn viết mã C coi các loại dữ liệu chỉ là các bit và byte thô, thì việc quét qua hệ thống loại (đây thực sự là lý do chính của tôi để sử dụng C ở vị trí đầu tiên), thì bạn không bao giờ muốn sử dụng mã đó để chống lại dữ liệu C ++ các loại có thể có các hàm tạo và hàm hủy sao chép và các con trỏ ảo và những thứ thuộc loại đó cần được tôn trọng. Vì vậy, nói chung nên là mã C sử dụng mã C của loại này, không phải mã C ++ sử dụng mã C của loại này.

Nếu bạn muốn mã C ++ của mình sử dụng mã C như vậy, thì thông thường bạn muốn sử dụng nó làm chi tiết triển khai của một lớp để chắc chắn rằng dữ liệu mà nó lưu trữ trong cấu trúc dữ liệu C chung là một kiểu dữ liệu không quan trọng để xây dựng và phá hủy. May mắn thay, có những đặc điểm kiểu như thế này mà bạn có thể sử dụng để kiểm tra xem trong xác nhận tĩnh, đảm bảo rằng các loại bạn đang lưu trữ trong cấu trúc dữ liệu C chung có các hàm hủy và hàm tạo không đáng kể và sẽ giữ nguyên như vậy.

Tôi có nên tìm cách viết lại tất cả trong C ++ không mang tất cả "trở lại" vào mã C?

Theo tôi, bạn không cần bận tâm nếu bạn tuân thủ các quy tắc trên và đảm bảo mã của bạn được kiểm tra tốt đối với các bài kiểm tra bạn đã viết. Phần có thể đáng để cải thiện là mã bạn đã viết bằng C ++ cho đến nay, nhưng điều đó không có nghĩa là bạn phải chuyển mã hoàn toàn dựa trên C sang C ++.

Với mã bạn đã viết bằng C ++ và biên dịch thành mã C ++, bạn phải cẩn thận. Sử dụng mã hóa giống như C trong C ++ là yêu cầu rắc rối. Ví dụ: nếu bạn phân bổ và giải phóng tài nguyên theo cách thủ công, rất có thể mã của bạn không an toàn ngoại lệ vì mã của bạn có thể gặp phải một ngoại lệ tại điểm mà bạn hoàn toàn thoát khỏi hàm trước khi bạn có thể sử freedụng tài nguyên. Trong C ++, RAII và những thứ như con trỏ thông minh không phải là sự tiện lợi hoàn hảo vì chúng có thể xuất hiện nếu bạn chỉ nhìn vào các đường thực thi thông thường mà không xem xét các đường dẫn đặc biệt. Chúng thường là một điều cần thiết cơ bản để viết mã an toàn ngoại lệ chính xác một cách đơn giản và hiệu quả mà sẽ không bắt đầu rò rỉ khắp nơi khi gặp ngoại lệ.

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.