Những thành ngữ C ++ nào không được dùng trong C ++ 11?


192

Với tiêu chuẩn mới, có nhiều cách làm mới, và nhiều cách đẹp hơn so với cách cũ, nhưng cách cũ vẫn ổn. Cũng rõ ràng rằng tiêu chuẩn mới không chính thức phản đối rất nhiều, vì lý do tương thích ngược. Vì vậy, câu hỏi còn lại là:

Những cách mã hóa cũ nào chắc chắn là thua kém các kiểu C ++ 11, và bây giờ chúng ta có thể làm gì?

Khi trả lời điều này, bạn có thể bỏ qua những điều hiển nhiên như "sử dụng biến tự động".


13
Bạn không thể phản đối thành ngữ.
Pubby

6
Cuộc nói chuyện của Herb Sutter tại Đi bản địa 2012 bao gồm điều này:
bames53

5
Trả lại giá trị không đổi không còn được khuyến khích. Rõ ràng auto_ptrlà bị phản đối, quá.
Kerrek SB

27
Tất nhiên là bạn có thể, Pubby. Trước khi các mẫu C ++ được phát minh, đã có một kỹ thuật macro để làm các mẫu. Sau đó, C ++ đã thêm chúng, và cách cũ được coi là xấu.
Alan Baljeu

7
Câu hỏi này thực sự cần phải được chuyển đến Lập trình viên.
Nicol Bolas

Câu trả lời:


173
  1. Lớp cuối cùng : C ++ 11 cung cấpfinal xác định để ngăn chặn dẫn xuất lớp
  2. C ++ 11 lambdas giảm đáng kể nhu cầu đối với các lớp đối tượng hàm (functor) được đặt tên.
  3. Move Con constructor: Những cách kỳ diệu trong đó std::auto_ptrcông việc không còn cần thiết do hỗ trợ hạng nhất cho các tham chiếu giá trị.
  4. Bool an toàn : Điều này đã được đề cập trước đó. Các toán tử rõ ràng của C ++ 11 đã loại bỏ thành ngữ C ++ 03 rất phổ biến này.
  5. Shrink-to-fit : Nhiều bộ chứa C ++ 11 STL cung cấp shrink_to_fit()chức năng thành viên, giúp loại bỏ nhu cầu hoán đổi tạm thời.
  6. Lớp cơ sở tạm thời : Một số thư viện C ++ cũ sử dụng thành ngữ khá phức tạp này. Với ngữ nghĩa di chuyển, nó không còn cần thiết nữa.
  7. Loại Enum Enum an toàn rất an toàn trong C ++ 11.
  8. Cấm phân bổ heap : = deleteCú pháp là cách trực tiếp hơn nhiều để nói rằng một chức năng cụ thể bị từ chối rõ ràng. Điều này được áp dụng để ngăn chặn phân bổ heap (nghĩa là =deletecho thành viên operator new), ngăn chặn các bản sao, chuyển nhượng, v.v.
  9. Templated typedef : Các mẫu bí danh trong C ++ 11 làm giảm nhu cầu đối với các typedefs templated đơn giản. Tuy nhiên, các trình tạo kiểu phức tạp vẫn cần các hàm meta.
  10. Một số tính toán thời gian biên dịch bằng số, chẳng hạn như Fibonacci có thể dễ dàng được thay thế bằng cách sử dụng các biểu thức hằng tổng quát
  11. result_of: Sử dụng mẫu lớp result_ofnên được thay thế bằng decltype. Tôi nghĩ rằng result_ofsử dụng decltypekhi nó có sẵn.
  12. Bộ khởi tạo thành viên trong lớp lưu kiểu gõ để khởi tạo mặc định của thành viên không tĩnh với giá trị mặc định.
  13. Trong mã C ++ 11 mới NULLnên được xác định lại là nullptr, nhưng hãy xem bài nói của STL để tìm hiểu lý do tại sao họ quyết định chống lại nó.
  14. Những người hâm mộ mẫu biểu thức rất vui mừng khi có cú pháp hàm trả về kiểu đuôi trong C ++ 11. Không có loại trả lại dài 30 dòng!

Tôi nghĩ tôi sẽ dừng ở đó!


Cảm ơn các công cụ chi tiết!
Alan Baljeu

7
Câu trả lời tuyệt vời, nhưng tôi sẽ đình công result_oftừ danh sách. Mặc dù cồng kềnh typenamecần thiết trước đó, tôi nghĩ typename result_of<F(Args...)::typeđôi khi có thể dễ đọc hơn decltype(std::declval<F>()(std::declval<Args>()...)và với sự chấp nhận của N3436 vào tài liệu làm việc mà cả hai đều làm việc cho SFINAE (trước đây là một lợi thế của decltypeviệc result_ofkhông cung cấp)
Jonathan Wakely

Về 14) Tôi vẫn khóc rằng tôi phải sử dụng macro để viết cùng một mã hai lần - một lần cho thân hàm và một lần cho câu lệnh dectype () ...

2
Tôi muốn lưu ý rằng chủ đề này được liên kết từ trang Microsoft này dưới dạng bài viết "Để biết thêm thông tin" trong phần giới thiệu chung về ngôn ngữ C ++, nhưng chủ đề này là một chủ đề chuyên môn cao! Tôi có thể gợi ý ngắn gọn "Chủ đề này KHÔNG dành cho người mới C ++!" lời khuyên được đưa vào đầu chủ đề hay câu trả lời này?
Aacini

Re 12: "Khởi tạo thành viên trong lớp" - đó là thành ngữ mới, không phải là thành ngữ không dùng nữa, phải không? Chuyển thứ tự câu có lẽ? Re 2: Functor rất hữu ích khi bạn muốn chuyển qua các kiểu hơn là các đối tượng (đặc biệt là trong các tham số mẫu). Vì vậy, nó chỉ là một số sử dụng của functor bị phản đối.
einpoklum

66

Tại một thời điểm, người ta đã lập luận rằng người ta nên trả về theo constgiá trị thay vì chỉ theo giá trị:

const A foo();
^^^^^

Điều này hầu như vô hại trong C ++ 98/03, và thậm chí có thể đã bắt gặp một vài lỗi trông giống như:

foo() = a;

Nhưng quay trở lại constbị chống chỉ định trong C ++ 11 vì nó ức chế ngữ nghĩa di chuyển:

A a = foo();  // foo will copy into a instead of move into it

Vì vậy, chỉ cần thư giãn và mã:

A foo();  // return by non-const value

9
Tuy nhiên, các lỗi có thể phòng ngừa có thể được bắt gặp bằng cách sử dụng vòng loại tham chiếu cho các chức năng. Chẳng hạn như trong trường hợp trên xác định A& operator=(A o)&thay vì A& operator=(A o). Những điều này ngăn ngừa những sai lầm ngớ ngẩn và làm cho các lớp hoạt động giống như các loại cơ bản và không ngăn chặn ngữ nghĩa di chuyển.
Joe

61

Ngay khi bạn có thể từ bỏ 0NULLủng hộ nullptr, hãy làm như vậy!

Trong mã không chung chung, việc sử dụng 0hoặc NULLkhông phải là một vấn đề lớn. Nhưng ngay khi bạn bắt đầu chuyển qua các hằng con trỏ null trong mã chung, tình huống sẽ nhanh chóng thay đổi. Khi bạn chuyển 0đến một template<class T> func(T) Tđược suy ra như một intvà không phải là một con trỏ null. Và nó không thể được chuyển đổi trở lại thành một con trỏ null sau đó. Dòng thác này rơi vào một loạt các vấn đề đơn giản là không tồn tại nếu vũ trụ chỉ được sử dụng nullptr.

C ++ 11 không phản đối 0NULLlà hằng số con trỏ null. Nhưng bạn nên mã như thể nó đã làm.


Dectype (nullptr) là gì?

4
@GrapschKnutsch: Chính là nó std::nullptr_t.
Howard Hinnant

Đề nghị điều này được đánh giá lại là thành ngữ không được chấp nhận thay vì quy ước mới để thông qua (ví dụ: "Việc sử dụng 0hoặc NULLcho con trỏ null").
einpoklum

38

Thành ngữ bool an toànexplicit operator bool().

Các nhà xây dựng bản sao riêng tư (boost :: không thể sao chép) → X(const X&) = delete

Mô phỏng lớp cuối cùng với hàm hủy riêng và kế thừa ảoclass X final


những ví dụ hay và súc tích, một trong số đó thậm chí còn mang từ "thành ngữ" trong đó. cũng được đặt
Sebastian Mach

2
Wow, tôi chưa bao giờ thấy 'thành ngữ bool an toàn' trước đây, nó trông khá kinh tởm! Tôi hy vọng tôi không bao giờ cần nó trong mã trước C ++ 11 ...
boycy 23/212

24

Một trong những điều khiến bạn tránh viết các thuật toán cơ bản trong C ++ 11 là sự sẵn có của lambdas kết hợp với các thuật toán được cung cấp bởi thư viện chuẩn.

Bây giờ tôi đang sử dụng những thứ đó và thật không thể tin được bạn thường chỉ nói những gì bạn muốn làm bằng cách sử dụng Count_if (), for_each () hoặc các thuật toán khác thay vì phải viết lại các vòng lặp chết tiệt.

Khi bạn đang sử dụng trình biên dịch C ++ 11 với thư viện chuẩn C ++ 11 hoàn chỉnh, bạn không còn lý do nào để không sử dụng các thuật toán tiêu chuẩn để xây dựng thư viện của mình . Lambda chỉ cần giết nó.

Tại sao?

Trong thực tế (sau khi tự mình sử dụng cách viết thuật toán này), việc đọc thứ gì đó được xây dựng bằng những từ đơn giản có nghĩa là những gì được thực hiện dễ dàng hơn nhiều so với một số vòng lặp mà bạn phải giải mã để biết ý nghĩa. Điều đó nói rằng, làm cho các đối số lambda tự động suy ra sẽ giúp rất nhiều làm cho cú pháp dễ dàng so sánh với một vòng lặp thô.

Về cơ bản, việc đọc các thuật toán được thực hiện với các thuật toán tiêu chuẩn dễ dàng hơn nhiều vì các từ ẩn các chi tiết thực hiện của các vòng lặp.

Tôi đoán chỉ có các thuật toán cấp cao hơn phải được suy nghĩ về việc chúng ta có các thuật toán cấp thấp hơn để xây dựng.


8
Trên thực tế có một cái cớ tốt. Bạn đang sử dụng thuật toán của Boost.Range , đẹp hơn nhiều;)
Nicol Bolas

10
Tôi không thấy rằng for_eachvới lambda thì tốt hơn so với vòng lặp dựa trên phạm vi tương đương, với nội dung của lambda trong vòng lặp. Mã trông ít nhiều giống nhau, nhưng lambda giới thiệu một số dấu câu bổ sung. Bạn có thể sử dụng tương đương những thứ như boost::irangeáp dụng nó cho nhiều vòng lặp hơn là những thứ rõ ràng sử dụng các trình vòng lặp. Ngoài ra, vòng lặp dựa trên phạm vi có tính linh hoạt cao hơn, trong đó bạn có thể thoát sớm nếu được yêu cầu (bằng returnhoặc bởi break), trong khi với for_eachbạn cần phải ném.
Steve Jessop

5
@SteveJessop: Mặc dù vậy, tính khả dụng của phạm vi dựa trên phạm vi forlàm cho it = c.begin(), const end = c.end(); it != end; ++itthành ngữ thông thường không còn tồn tại.
Ben Voigt

7
@SteveJessop Một lợi thế của for_eachthuật toán trong phạm vi dựa trên vòng lặp là bạn không thể break hoặc return. Đó là, khi bạn nhìn thấy for_eachbạn biết ngay lập tức mà không cần nhìn vào cơ thể rằng không có sự khó khăn như vậy.
bames53

5
@Klaim: cụ thể, tôi đang so sánh ví dụ std::for_each(v.begin(), v.end(), [](int &i) { ++i; });với for (auto &i : v) { ++i; }. Tôi chấp nhận rằng tính linh hoạt là hai lưỡi ( gotorất linh hoạt, đó là vấn đề). Tôi không nghĩ rằng các hạn chế của việc không thể sử dụng breaktrong for_eachđền bù phiên bản dành cho tính cách rườm rà thêm nó đòi hỏi - những người sử dụng for_eachở đây được IMO hy sinh khả năng đọc thực tế và thuận tiện cho một loại khái niệm lý thuyết rằng for_eachvề nguyên tắc rõ ràng và khái niệm đơn giản hơn. Trong thực tế, nó không rõ ràng hơn hoặc đơn giản hơn.
Steve Jessop

10

Bạn sẽ cần phải thực hiện các phiên bản tùy chỉnh swapít thường xuyên hơn. Trong C ++ 03, việc ném không hiệu quả swapthường là cần thiết để tránh tốn kém và ném các bản sao, và vì std::swapsử dụng hai bản sao, swapthường phải được tùy chỉnh. Trong C ++, std::swapsử dụng move, và do đó, trọng tâm chuyển sang thực hiện các hàm tạo di chuyển hiệu quả và không ném và các toán tử gán chuyển. Vì các mặc định này thường chỉ tốt, nên công việc này sẽ ít hơn nhiều so với trong C ++ 03.

Nói chung, thật khó để dự đoán những thành ngữ nào sẽ được sử dụng vì chúng được tạo ra thông qua kinh nghiệm. Chúng ta có thể mong đợi "C ++ 11 hiệu quả" có thể vào năm tới và "Tiêu chuẩn mã hóa C ++ 11" chỉ trong ba năm vì chưa có kinh nghiệm cần thiết.


1
Tôi nghi ngờ về điều này. Phong cách được đề xuất là sử dụng trao đổi để di chuyển và sao chép xây dựng, nhưng không phải là std :: exchange vì đó sẽ là hình tròn.
Alan Baljeu

Vâng, nhưng hàm tạo di chuyển thường gọi một hoán đổi tùy chỉnh hoặc về cơ bản là tương đương.
Nghịch đảo

2

Tôi không biết tên của nó, nhưng mã C ++ 03 thường sử dụng cấu trúc sau đây để thay thế cho việc chuyển nhượng bị thiếu:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Điều này tránh bất kỳ sao chép do bầu cử sao chép kết hợp với swapở trên.


1
Trong ví dụ của bạn, việc hoán đổi là không cần thiết, sao chép bản sao sẽ xây dựng giá trị trả về bằng mapmọi cách. Kỹ thuật bạn thể hiện là hữu ích nếu mapđã tồn tại, thay vì chỉ được xây dựng. Ví dụ sẽ tốt hơn nếu không có bình luận "nhà xây dựng mặc định giá rẻ" và với "// ..." giữa công trình đó và việc hoán đổi
Jonathan Wakely

Tôi đã thay đổi nó theo đề nghị của bạn. Cảm ơn.
Andrzej

Việc sử dụng "lớn" và "Lớn hơn" là khó hiểu. Tại sao không giải thích làm thế nào kích thước của khóa và loại giá trị quan trọng?
einpoklum

1

Khi tôi nhận thấy rằng trình biên dịch sử dụng chuẩn C ++ 11 không còn lỗi mã sau:

std::vector<std::vector<int>> a;

vì được cho là chứa toán tử >>, tôi bắt đầu nhảy. Trong các phiên bản trước, người ta sẽ phải làm

std::vector<std::vector<int> > a;

Để làm cho vấn đề tồi tệ hơn, nếu bạn từng phải gỡ lỗi này, bạn sẽ biết các thông báo lỗi phát sinh từ đây khủng khiếp đến mức nào.

Tôi, tuy nhiên, không biết điều này là "rõ ràng" với bạn.


1
Tính năng này đã được thêm vào trong C ++ trước đó. Hoặc ít nhất là Visual C ++ đã triển khai nó theo các cuộc thảo luận tiêu chuẩn nhiều năm trước đó.
Alan Baljeu

1
@AlanBaljeu Tất nhiên, có nhiều thứ không chuẩn được thêm vào trình biên dịch / thư viện. Có hàng tấn trình biên dịch có khai báo biến "tự động" trước C ++ 11, nhưng sau đó bạn không thể chắc chắn rằng mã của bạn thực sự có thể được biên dịch bởi bất kỳ thứ gì khác. Câu hỏi là về tiêu chuẩn, không phải về "có trình biên dịch nào có thể làm được điều này không".
v010dya

1

Trả về theo giá trị không còn là vấn đề. Với ngữ nghĩa di chuyển và / hoặc tối ưu hóa giá trị trả về (phụ thuộc vào trình biên dịch), các hàm mã hóa sẽ tự nhiên hơn mà không phải trả phí hoặc chi phí (hầu hết thời gian).


... nhưng thành ngữ nào đã bị phản đối?
einpoklum

Không phải là một thành ngữ nhưng nó là một thực hành tốt không cần thiết nữa. Ngay cả với trình biên dịch được hỗ trợ RVO là tùy chọn. vi.wikipedia.org/wiki/Return_value_optimization "Trong giai đoạn đầu của quá trình phát triển C ++, ngôn ngữ không có khả năng trả về hiệu quả một đối tượng của loại lớp từ một hàm được coi là một điểm yếu ....." struct Data {char byte [ 16]; }; void f (Dữ liệu * p) {// tạo kết quả trực tiếp trong * p} int main () {Dữ liệu d; f (& d); }
Martin A

Tôi đã gợi ý bạn nên diễn đạt câu trả lời của bạn là "phong tục tránh trả lại theo giá trị không còn phù hợp như v.v. v.v."
einpoklum
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.