Có phải tự động làm cho mã C ++ khó hiểu hơn?


122

Tôi đã xem một hội nghị của Herb Sutter, nơi ông khuyến khích mọi lập trình viên C ++ sử dụng auto.

Tôi đã phải đọc mã C # một thời gian trước, nơi varđược sử dụng rộng rãi và mã rất khó hiểu, mỗi khi varđược sử dụng, tôi phải kiểm tra kiểu trả về của bên phải. Đôi khi hơn một lần, vì tôi đã quên loại biến sau một thời gian!

Tôi biết trình biên dịch biết loại và tôi không phải viết nó, nhưng nó được chấp nhận rộng rãi rằng chúng ta nên viết mã cho các lập trình viên, không phải cho trình biên dịch.

Tôi cũng biết rằng dễ viết hơn:

auto x = GetX();

Hơn:

someWeirdTemplate<someOtherVeryLongNameType, ...>::someOtherLongType x = GetX();

Nhưng điều này chỉ được viết một lần và GetX()loại trả về được kiểm tra nhiều lần để hiểu loại nào xcó.

Điều này làm tôi băn khoăn không autolàm cho mã C ++ khó hiểu hơn?


29
Bạn có thực sự cần phải kiểm tra loại trả lại mỗi lần ? Tại sao không phải là loại rõ ràng từ mã? autothường có thể làm cho mọi thứ khó đọc hơn khi chúng khó đọc, nghĩa là các hàm quá dài, các biến được đặt tên kém, v.v.
R. Martinho Fernandes

25
"Nghệ thuật" sử dụng autorất giống như xác định khi nào nên sử dụng typedef. Tùy thuộc vào bạn để xác định khi nào nó cản trở và khi nào nó giúp.
ahenderson

18
Tôi nghĩ rằng tôi có cùng một vấn đề, nhưng sau đó tôi nhận ra rằng tôi chỉ có thể hiểu mã mà không cần biết các loại. ví dụ: "auto idx = get_index ();" vì vậy idx là một cái gì đó giữ một chỉ số. Loại chính xác là gì, không liên quan cho hầu hết các trường hợp.
PlasmaHH

31
Vì vậy, đừng viết auto x = GetX();, chọn một cái tên hay hơn cái tên xthực sự cho bạn biết nó làm gì trong bối cảnh cụ thể đó ... dù sao nó thường hữu ích hơn loại của nó.
Jonathan Wakely

11
Nếu sử dụng nhiều suy luận kiểu khiến lập trình viên khó đọc mã, thì mã hoặc lập trình viên cần cải thiện nghiêm trọng.
CA McCann

Câu trả lời:


100

Câu trả lời ngắn gọn: Hoàn toàn hơn, ý kiến ​​hiện tại của tôi về autoviệc bạn nên sử dụng autotheo mặc định trừ khi bạn rõ ràng muốn chuyển đổi. (Chính xác hơn một chút, "... trừ khi bạn muốn cam kết rõ ràng với một loại, điều gần như luôn luôn là vì bạn muốn chuyển đổi.")

Câu trả lời dài hơn và lý do:

Viết một loại rõ ràng (chứ không phải auto) chỉ khi bạn thực sự muốn cam kết rõ ràng với một loại, điều này gần như luôn luôn có nghĩa là bạn muốn rõ ràng nhận được chuyển đổi sang loại đó. Ngoài đỉnh đầu, tôi nhớ lại hai trường hợp chính:

  • (Chung) Sự initializer_listngạc nhiên mà auto x = { 1 };suy ra initializer_list. Nếu bạn không muốn initializer_list, hãy nói loại - tức là yêu cầu chuyển đổi rõ ràng.
  • (Hiếm) Trường hợp mẫu biểu thức, chẳng hạn như auto x = matrix1 * matrix 2 + matrix3;chụp một loại trình trợ giúp hoặc proxy không được hiển thị cho người lập trình. Trong nhiều trường hợp, việc nắm bắt loại đó là tốt và lành tính, nhưng đôi khi nếu bạn thực sự muốn nó sụp đổ và thực hiện tính toán thì hãy nói loại đó - tức là, một lần nữa yêu cầu chuyển đổi một cách rõ ràng.

Thường xuyên sử dụng autotheo mặc định, bởi vì sử dụng autođể tránh những cạm bẫy và làm cho mã của bạn chính xác hơn, dễ bảo trì hơn và mạnh mẽ hơn và hiệu quả hơn. Theo thứ tự từ hầu hết đến quan trọng nhất, theo tinh thần "viết cho rõ ràng và chính xác trước":

  • Tính chính xác: Sử dụng autođảm bảo bạn sẽ có được loại đúng. Như đã nói, nếu bạn lặp lại chính mình (nói loại dư thừa), bạn có thể và sẽ nói dối (hiểu sai). Đây là một ví dụ thông thường: void f( const vector<int>& v ) { for( /*…*- tại thời điểm này, nếu bạn viết một cách rõ ràng kiểu lặp, bạn muốn nhớ viết const_iterator(phải không?), Trong khi autochỉ cần làm cho đúng.
  • Khả năng duy trì và mạnh mẽ: Việc sử dụng autolàm cho mã của bạn mạnh mẽ hơn khi đối mặt với sự thay đổi, bởi vì khi loại biểu thức thay đổi, autosẽ tiếp tục phân giải thành loại chính xác. Thay vào đó, nếu bạn cam kết với một loại rõ ràng, việc thay đổi loại biểu thức sẽ thực hiện chuyển đổi im lặng khi loại mới chuyển đổi sang loại cũ hoặc không cần xây dựng phá vỡ khi loại mới vẫn hoạt động - như loại cũ nhưng không chuyển đổi sang loại cũ loại (ví dụ, khi bạn thay đổi một mapđến một unordered_map, mà luôn luôn là tốt nếu bạn không dựa vào trật tự, sử dụng autocho vòng lặp của bạn, bạn sẽ chuyển đổi liền mạch từ map<>::iteratorđể unordered_map<>::iterator, nhưng sử dụngmap<>::iterator rõ ràng ở mọi nơi có nghĩa là bạn sẽ lãng phí thời gian quý báu của mình vào một gợn sửa lỗi mã cơ học, trừ khi một thực tập viên đi ngang qua và bạn có thể bỏ qua công việc nhàm chán trên chúng).
  • Hiệu suất:autođảm bảo không có chuyển đổi ngầm định sẽ xảy ra, nó đảm bảo hiệu suất tốt hơn theo mặc định. Thay vào đó, nếu bạn nói loại đó và nó yêu cầu chuyển đổi, bạn sẽ thường âm thầm nhận chuyển đổi cho dù bạn có mong đợi hay không.
  • Tính khả dụng: Sử dụng autolà lựa chọn tốt duy nhất của bạn cho các loại khó đánh vần và không gây nhiễu, chẳng hạn như lambdas và trình trợ giúp mẫu, không dùng đến các decltypebiểu thức lặp đi lặp lại hoặc các chỉ dẫn kém hiệu quả như std::function.
  • Thuận tiện: Và, vâng, autolà ít gõ. Tôi đề cập đến điều đó cuối cùng vì nó là lý do phổ biến để thích nó, nhưng nó không phải là lý do lớn nhất để sử dụng nó.

Do đó: Thích nói autotheo mặc định. Nó cung cấp rất nhiều tính đơn giản và hiệu suất và độ rõ ràng đến mức bạn chỉ tự làm tổn thương chính mình (và những người duy trì tương lai của mã của bạn) nếu bạn không. Chỉ cam kết với một loại rõ ràng khi bạn thực sự có nghĩa là nó, gần như luôn luôn có nghĩa là bạn muốn chuyển đổi rõ ràng.

Vâng, có (bây giờ) một GotW về điều này.


14
Tôi thấy tự động hữu ích ngay cả khi tôi muốn chuyển đổi. Nó cho phép tôi yêu cầu chuyển đổi một cách rõ ràng mà không cần lặp lại kiểu : auto x = static_cast<X>(y). Điều này static_castlàm rõ rằng việc chuyển đổi là có chủ đích và nó tránh các cảnh báo của trình biên dịch về việc chuyển đổi. Thông thường tránh cảnh báo trình biên dịch không tốt lắm, nhưng tôi không nhận được cảnh báo về chuyển đổi mà tôi đã cân nhắc cẩn thận khi viết static_cast. Mặc dù tôi sẽ không làm điều này nếu không có cảnh báo ngay bây giờ nhưng tôi muốn nhận được cảnh báo trong tương lai nếu các loại thay đổi theo cách nguy hiểm tiềm tàng.
Bjarke Hammersholt Roune

6
Một điều tôi tìm thấy autolà chúng ta nên cố gắng lập trình chống lại các giao diện (không phải theo nghĩa OOP), không chống lại việc triển khai cụ thể. Thật sự giống với các mẫu. Bạn có phàn nàn về "mã khó đọc" bởi vì bạn có một tham số loại mẫu Tđược sử dụng ở mọi nơi không? Không, tôi không nghĩ vậy. Trong các mẫu cũng vậy, chúng tôi mã hóa theo một giao diện, gõ vịt thời gian biên dịch là cái mà nhiều người gọi nó.
Xèo

6
"Sử dụng tự động đảm bảo bạn sẽ có được loại đúng." Không đúng chút nào. Nó chỉ đảm bảo bạn sẽ có được loại được quy định bởi một số phần khác trong mã của bạn. Điều đó có đúng hay không hoàn toàn không rõ ràng khi bạn giấu nó đi phía sau auto.
Các cuộc đua nhẹ nhàng trong quỹ đạo

Tôi thực sự ngạc nhiên khi không ai quan tâm đến IDE ... Ngay cả các IDE hiện đại cũng không hỗ trợ chính xác việc chuyển sang định nghĩa lớp / cấu trúc trong trường hợp autobiến, nhưng hầu hết tất cả chúng đều làm đúng với đặc tả kiểu rõ ràng. Không ai sử dụng IDE? Mọi người chỉ sử dụng biến int / float / bool? Mọi người đều thích tài liệu bên ngoài cho các thư viện thay vì các tiêu đề tự viết tài liệu?
avtomaton

rằng GotW: Herbutter.com/2013/08/12/NH Tôi không thấy "bất ngờ khởi đầu" đó là một bất ngờ như thế nào; niềng răng xung quanh =RHS không có ý nghĩa nhiều bởi bất kỳ cách giải thích nào khác (thanh danh sách khởi động thanh, nhưng bạn cần biết những gì bạn đang khởi tạo, đó là một oxymoron với auto). Một trong đó đáng ngạc nhiên là auto i{1}cũng suy luận initializer_list, mặc dù ngụ ý không thực hiện việc này chuẩn bị tinh thần init-list mà là lấy biểu thức này và sử dụng loại của nó ... nhưng chúng tôi nhận initializer_listcó quá. Rất may, C ++ 17 sửa chữa tất cả điều này tốt lên.
gạch dưới

112

Đó là một tình huống từng trường hợp.

Nó đôi khi làm cho mã khó hiểu hơn, đôi khi không. Lấy ví dụ:

void foo(const std::map<int, std::string>& x)
{
   for ( auto it = x.begin() ; it != x.end() ; it++ )
   { 
       //....
   }
}

chắc chắn là dễ hiểu chắc chắn dễ viết hơn so với khai báo lặp thực tế.

Tôi đã sử dụng C ++ được một thời gian rồi, nhưng tôi có thể đảm bảo rằng tôi đã gặp lỗi trình biên dịch trong lần chụp đầu tiên của mình vì tôi đã quên const_iteratorvà ban đầu sẽ sử dụng iterator... :)

Tôi sẽ sử dụng nó cho các trường hợp như thế này, nhưng không phải nơi nó thực sự làm khó loại hình này (như tình huống của bạn), nhưng điều này hoàn toàn chủ quan.


45
Chính xác. Ai heck quan tâm đến các loại. Đó là một trình vòng lặp. Tôi không quan tâm đến loại, tất cả những gì tôi cần biết là tôi có thể sử dụng nó để lặp lại.
R. Martinho Fernandes

5
+1. Ngay cả khi bạn đã đặt tên cho loại đó, bạn sẽ đặt tên nó là std::map<int, std::string>::const_iterator, vì vậy dù sao thì tên đó cũng không cho bạn biết nhiều về loại này.
Steve Jessop

4
@SteveJessop: Nó cho tôi biết ít nhất hai điều: khóa là intvà giá trị là std::string. :)
Nawaz

16
@Nawaz: và bạn không thể chỉ định it->secondvì đó là trình lặp const. Tất cả thông tin đó là sự lặp lại của những gì trong dòng trước đó , const std::map<int, std::string>& x. Nói nhiều lần đôi khi thông tin tốt hơn, nhưng không có nghĩa là đó là một quy tắc chung :-)
Steve Jessop

11
TBH Tôi muốn for (anX : x)làm cho nó rõ ràng hơn nữa, chúng tôi chỉ lặp đi lặp lại x. Trường hợp bình thường khi bạn cần một trình vòng lặp là khi bạn sửa đổi vùng chứa, nhưng xconst&
MSalters

94

Nhìn vào khía cạnh khác. Bạn có viết không:

std::cout << (foo() + bar()) << "\n";

hoặc là:

// it is important to know the types of these values
int f = foo();
size_t b = bar();
size_t total = f + b;

std::cout << total << "\n";

Đôi khi nó không giúp đánh vần loại rõ ràng.

Quyết định xem bạn có cần đề cập đến loại không giống như quyết định xem bạn có muốn phân tách mã qua nhiều câu lệnh hay không bằng cách xác định các biến trung gian. Trong C ++ 03 cả hai đã được liên kết, bạn có thể nghĩ autonhư một cách để tách chúng ra.

Đôi khi làm cho các loại rõ ràng có thể hữu ích:

// seems legit    
if (foo() < bar()) { ... }

so với

// ah, there's something tricky going on here, a mixed comparison
if ((unsigned int)foo() < bar()) { ... }

Trong trường hợp bạn khai báo một biến, việc sử dụng autocho phép kiểu này không được nói giống như trong nhiều biểu thức. Có lẽ bạn nên cố gắng tự quyết định khi điều đó giúp dễ đọc và khi nó cản trở.

Bạn có thể lập luận rằng việc trộn các loại đã ký và không dấu là một lỗi bắt đầu (thực sự, một số người lập luận thêm rằng người ta không nên sử dụng các loại không dấu). Các lý do nó được cho là một sai lầm là nó làm cho các loại của toán hạng cực kỳ quan trọng vì các hành vi khác nhau. Nếu đó là một điều xấu khi cần biết các loại giá trị của bạn, thì có lẽ đó cũng không phải là điều xấu khi không cần biết chúng. Vì vậy, với điều kiện là mã không gây nhầm lẫn vì những lý do khác, điều đó làm cho autoOK, phải không? ;-)

Đặc biệt khi viết mã chung, có những trường hợp loại thực tế của một biến không quan trọng, điều quan trọng là nó đáp ứng giao diện bắt buộc. Vì vậy, autocung cấp một mức độ trừu tượng trong đó bạn bỏ qua loại (nhưng tất nhiên trình biên dịch không, nó biết). Làm việc ở một mức độ trừu tượng phù hợp có thể giúp khả năng đọc khá nhiều, làm việc ở mức "sai" làm cho việc đọc mã trở thành một vấn đề.


21
+1 autocho phép bạn tạo các biến được đặt tên với các loại không thể đặt tên hoặc không quan tâm. Tên có ý nghĩa có thể hữu ích.
Mankude

Trộn ký và không dấu nếu bạn sử dụng không dấu cho việc sử dụng đúng: số học mô-đun. Không phải là nếu bạn sử dụng sai dấu không dấu cho số nguyên dương. Hầu như không có chương trình nào được sử dụng cho unsign, nhưng ngôn ngữ cốt lõi buộc định nghĩa inane của nó sizeoflà không dấu đối với bạn.
tò mò

27

IMO, bạn đang xem xét điều này khá nhiều ngược lại.

Đây không phải là vấn đề autodẫn đến mã không thể đọc được hoặc thậm chí ít đọc hơn. Vấn đề là (hy vọng rằng) có một loại rõ ràng cho giá trị trả về sẽ bù cho thực tế là nó (rõ ràng) không rõ loại nào sẽ được trả về bởi một số chức năng cụ thể.

Ít nhất theo ý kiến ​​của tôi, nếu bạn có một hàm có kiểu trả về không rõ ràng ngay lập tức, thì đó là vấn đề của bạn ngay tại đó. Những gì hàm làm rõ ràng từ tên của nó và loại giá trị trả về nên rõ ràng từ những gì nó làm. Nếu không, đó là nguồn gốc thực sự của vấn đề.

Nếu có một vấn đề ở đây, thì không auto. Đó là với phần còn lại của mã, và rất có thể là loại rõ ràng chỉ đủ hỗ trợ băng tần để giúp bạn không nhìn thấy và / hoặc khắc phục vấn đề cốt lõi. Khi bạn đã khắc phục vấn đề thực sự đó, khả năng đọc mã sử dụng autothường sẽ ổn.

Tôi cho rằng trong sự công bằng tôi nên thêm vào: Tôi đã xử lý một vài trường hợp trong đó những điều đó không rõ ràng như bạn muốn, và khắc phục vấn đề cũng khá khó lường. Chỉ lấy một ví dụ, tôi đã làm một số tư vấn cho một công ty vài năm trước đây đã sáp nhập với một công ty khác. Họ đã kết thúc với một cơ sở mã được "xô đẩy" hơn là thực sự hợp nhất. Các chương trình cấu thành đã bắt đầu sử dụng các thư viện khác nhau (nhưng khá giống nhau) cho các mục đích tương tự, và mặc dù chúng đang làm việc để hợp nhất mọi thứ sạch sẽ hơn, chúng vẫn làm. Trong một số lượng lớn các trường hợp, cách duy nhất để đoán loại nào sẽ được trả về bởi một hàm đã cho là biết hàm đó có nguồn gốc từ đâu.

Ngay cả trong trường hợp như vậy, bạn có thể giúp làm cho khá nhiều điều rõ ràng hơn. Trong trường hợp đó, tất cả các mã bắt đầu trong không gian tên toàn cầu. Chỉ cần di chuyển một số tiền hợp lý vào một số không gian tên đã loại bỏ các xung đột tên và giảm bớt theo dõi kiểu khá nhiều.


17

Có một số lý do tại sao tôi không thích tự động cho sử dụng chung:

  1. Bạn có thể cấu trúc lại mã mà không sửa đổi nó. Vâng, đây là một trong những điều thường được liệt kê là một lợi ích của việc sử dụng tự động. Chỉ cần thay đổi kiểu trả về của hàm và nếu tất cả các mã gọi nó sử dụng tự động, thì không cần nỗ lực thêm! Bạn nhấn biên dịch, nó xây dựng - 0 cảnh báo, 0 lỗi - và bạn chỉ cần tiếp tục và kiểm tra mã của mình mà không phải đối phó với sự lộn xộn khi xem qua và có khả năng sửa đổi 80 vị trí mà chức năng được sử dụng.

Nhưng chờ đã, đó có thực sự là một ý tưởng tốt? Điều gì xảy ra nếu loại quan trọng trong nửa tá các trường hợp sử dụng đó và bây giờ mã đó thực sự hoạt động khác nhau? Điều này cũng có thể phá vỡ đóng gói, bằng cách sửa đổi không chỉ các giá trị đầu vào, mà chính hành vi của việc thực hiện riêng tư của các lớp khác gọi hàm.

1a. Tôi là người tin tưởng vào khái niệm "mã tự ghi". Lý do đằng sau mã tự viết tài liệu là các bình luận có xu hướng trở nên lỗi thời, không còn phản ánh những gì mã đang làm, trong khi bản thân mã - nếu được viết theo cách rõ ràng - là tự giải thích, luôn cập nhật về ý định của nó, và sẽ không khiến bạn bối rối với những bình luận cũ. Nếu các loại có thể được thay đổi mà không cần phải sửa đổi chính mã, thì chính mã / biến có thể trở nên cũ. Ví dụ:

tự động bThreadOK = CheckThreadHealth ();

Ngoại trừ vấn đề là CheckThreadHealth () tại một số điểm đã được tái cấu trúc để trả về giá trị enum cho biết trạng thái lỗi, nếu có, thay vì bool. Nhưng người thực hiện thay đổi đó đã bỏ lỡ việc kiểm tra dòng mã đặc biệt này và trình biên dịch không giúp ích được gì vì nó được biên dịch mà không có cảnh báo hoặc lỗi.

  1. Bạn có thể không bao giờ biết các loại thực tế là gì. Điều này cũng thường được liệt kê như là một "lợi ích" chính của ô tô. Tại sao tìm hiểu những gì một chức năng đang cung cấp cho bạn, khi bạn chỉ có thể nói, "Ai quan tâm? Nó biên dịch!"

Nó thậm chí là loại công trình, có lẽ. Tôi nói là loại công việc, bởi vì mặc dù bạn đang tạo một bản sao cấu trúc 500 byte cho mỗi lần lặp, vì vậy bạn có thể kiểm tra một giá trị duy nhất trên đó, mã vẫn hoàn toàn hoạt động. Vì vậy, ngay cả các bài kiểm tra đơn vị của bạn cũng không giúp bạn nhận ra rằng mã xấu đang ẩn đằng sau chiếc ô tô trông đơn giản và ngây thơ đó. Hầu hết những người khác quét qua tệp sẽ không nhận thấy nó ngay từ cái nhìn đầu tiên.

Điều này cũng có thể trở nên tồi tệ hơn nếu bạn không biết loại đó là gì, nhưng bạn chọn một tên biến đưa ra một giả định sai về nó là gì, thực tế đạt được kết quả tương tự như trong 1a, nhưng ngay từ đầu thay vì hậu tái cấu trúc.

  1. Nhập mã khi ban đầu viết nó không phải là phần tốn thời gian nhất của lập trình. Có, tự động làm cho việc viết một số mã nhanh hơn ban đầu. Từ chối trách nhiệm, tôi gõ> 100 WPM, vì vậy có lẽ nó không làm phiền tôi nhiều như những người khác. Nhưng nếu tất cả những gì tôi phải làm là viết mã mới cả ngày, tôi sẽ là một người cắm trại hạnh phúc. Phần tốn thời gian nhất của lập trình là chẩn đoán các lỗi khó tái tạo, trường hợp biên trong mã, thường xuất phát từ các vấn đề không rõ ràng - chẳng hạn như việc lạm dụng tự động có thể được đưa ra (tham chiếu so với sao chép, đã ký so với không dấu, float so với int, bool vs. con trỏ, v.v.).

Đối với tôi có vẻ rõ ràng rằng auto được giới thiệu chủ yếu như một cách giải quyết cho cú pháp khủng khiếp với các kiểu mẫu thư viện chuẩn. Thay vì cố gắng sửa cú pháp mẫu mà mọi người đã quen thuộc - điều này cũng gần như không thể thực hiện được vì tất cả các mã hiện có mà nó có thể phá vỡ - hãy thêm vào một từ khóa về cơ bản che giấu vấn đề. Về cơ bản những gì bạn có thể gọi là "hack".

Tôi thực sự không có bất kỳ sự bất đồng nào với việc sử dụng tự động với các thùng chứa thư viện tiêu chuẩn. Đó rõ ràng là những gì từ khóa được tạo ra và các chức năng trong thư viện tiêu chuẩn không có khả năng thay đổi về cơ bản mục đích (hoặc loại cho vấn đề đó), làm cho tự động tương đối an toàn để sử dụng. Nhưng tôi sẽ rất thận trọng khi sử dụng nó với mã và giao diện của riêng bạn có thể biến động hơn nhiều và có thể chịu những thay đổi cơ bản hơn.

Một ứng dụng hữu ích khác của tự động giúp tăng cường khả năng của ngôn ngữ là tạo ra các thời gian trong các macro không xác định kiểu. Đây là điều bạn không thể thực sự làm trước đây, nhưng bạn có thể làm ngay bây giờ.


4
Bạn đóng đinh nó. Ước gì tôi có thể cho cái này +2.
cmaster

Một "tốt được thận trọng" -answer. @cmaster: Có đấy.
Ded repeatator

Tôi đã tìm thấy một trường hợp hữu ích hơn : auto something = std::make_shared<TypeWithLongName<SomeParam>>(a,b,c);. :-)
Notinlist

14

Có, việc biết loại biến của bạn sẽ dễ dàng hơn nếu bạn không sử dụng auto. Câu hỏi là: bạn có cần biết loại biến của mình để đọc mã không? Đôi khi câu trả lời sẽ là có, đôi khi không. Ví dụ, khi nhận được một trình vòng lặp từ a std::vector<int>, bạn có cần biết rằng đó là một std::vector<int>::iteratorhoặc sẽ auto iterator = ...;đủ? Mọi thứ mà bất kỳ ai cũng muốn làm với một trình vòng lặp đều được đưa ra bởi thực tế đó là một trình vòng lặp - nó không quan trọng là loại cụ thể là gì.

Sử dụng autotrong những tình huống khi nó không làm cho mã của bạn khó đọc hơn.


12

Cá nhân tôi autochỉ sử dụng khi nó hoàn toàn rõ ràng đối với lập trình viên.

ví dụ 1

std::map <KeyClass, ValueClass> m;
// ...
auto I = m.find (something); // OK, find returns an iterator, everyone knows that

Ví dụ 2

MyClass myObj;
auto ret = myObj.FindRecord (something)// NOT OK, everyone needs to go and check what FindRecord returns

5
Đây là một ví dụ rõ ràng về việc đặt tên xấu làm tổn thương khả năng đọc, không thực sự tự động. Không ai có ý tưởng mờ nhạt nhất "DoS Something Weird" làm gì, vì vậy sử dụng tự động hay không sẽ không làm cho nó dễ đọc hơn nữa. Bạn sẽ phải kiểm tra các tài liệu một trong hai cách.
R. Martinho Fernandes

4
Ok, bây giờ có phần tốt hơn. Tôi vẫn tìm thấy biến được đặt tên kém, mặc dù, điều đó vẫn còn đau. Nếu bạn viết auto record = myObj.FindRecord(something)nó sẽ rõ ràng rằng loại biến là bản ghi. Hoặc đặt tên cho nó ithoặc tương tự sẽ làm cho nó rõ ràng nó trả về một trình vòng lặp. Lưu ý rằng, ngay cả khi bạn không sử dụng auto, việc đặt tên biến chính xác có nghĩa là bạn không cần phải quay lại khai báo để xem loại từ bất kỳ đâu trong hàm . Tôi đã gỡ bỏ downvote của mình vì ví dụ bây giờ không phải là một người rơm hoàn chỉnh, nhưng tôi vẫn không mua đối số ở đây.
R. Martinho Fernandes

2
Để thêm vào @ R.MartinhoFernandes: câu hỏi là, nó có thực sự quan trọng bây giờ "bản ghi" chính xác là gì không? Tôi nghĩ điều quan trọng hơn đó là bản ghi, loại nguyên thủy cơ bản thực tế là một lớp trừu tượng khác .. Vì vậy, một người không có tự động có thể có:MyClass::RecordTy record = myObj.FindRecord (something)
paul23

2
@ paul23: Điều gì làm việc sử dụng tự động so với loại có được bạn, sau đó, nếu phản đối duy nhất của bạn là "Tôi không biết cách sử dụng này". Hoặc là làm cho bạn tìm kiếm nó anyway.
GManNickG

3
@GManNickG nó cho tôi biết loại không quan trọng chính xác.
paul23

10

Câu hỏi này lấy ý kiến, sẽ thay đổi từ lập trình viên sang lập trình viên, nhưng tôi sẽ nói không. Trong thực tế, trong nhiều trường hợp ngược lại, autocó thể giúp làm cho mã dễ hiểu hơn bằng cách cho phép lập trình viên tập trung vào logic hơn là các chi tiết vụn vặt.

Điều này đặc biệt đúng khi đối mặt với các loại mẫu phức tạp. Dưới đây là một ví dụ đơn giản hóa và có kế hoạch. Cái nào dễ hiểu hơn?

for( std::map<std::pair<Foo,Bar>, std::pair<Baz, Bot>, std::less<BazBot>>::const_iterator it = things_.begin(); it != things_.end(); ++it )

.. hoặc là...

for( auto it = things_.begin(); it != things_.end(); ++it )

Một số người sẽ nói cái thứ hai dễ hiểu hơn, những người khác có thể nói cái thứ nhất. Tuy nhiên, những người khác có thể nói rằng việc sử dụng vô cớ autocó thể góp phần làm giảm bớt các lập trình viên sử dụng nó, nhưng đó là một câu chuyện khác.


4
+1 Haha, mọi người đều trình bày các std::mapví dụ, ngoài ra với các đối số mẫu phức tạp.
Nawaz

1
@Nawaz: Thật dễ dàng để đưa ra các tên mẫu dài điên bằng maps. :)
John Dibling

@Nawaz: nhưng tôi tự hỏi tại sao sau đó không có ai đến với phạm vi dựa trên các vòng lặp như là sự thay thế tốt hơn và dễ đọc hơn ...
PlasmaHH

1
@PlasmaHH, không phải tất cả các vòng lặp với các trình vòng lặp đều có thể được thay thế bằng phạm vi dựa trên phạm vi, forví dụ: nếu các trình vòng lặp bị vô hiệu trong thân vòng lặp và do đó cần phải được tăng trước hoặc không tăng lên chút nào.
Jonathan Wakely

@PlasmaHH: Trong trường hợp của tôi, MSVC10 không thực hiện dựa trên phạm vi cho các vòng lặp. Vì MSVC10 là bản thử nghiệm C ++ 11 của tôi, tôi thực sự không có nhiều kinh nghiệm với chúng.
John Dibling

8

Nhiều câu trả lời hay cho đến nay, nhưng để tập trung vào câu hỏi ban đầu, tôi nghĩ Herb đi quá xa trong lời khuyên của mình để sử dụng automột cách tự do. Ví dụ của bạn là một trường hợp sử dụng autorõ ràng làm tổn thương khả năng đọc. Một số người khẳng định đó không phải là vấn đề với các IDE hiện đại, nơi bạn có thể di chuột qua một biến và xem loại, nhưng tôi không đồng ý: ngay cả những người luôn sử dụng IDE đôi khi cũng cần xem xét các đoạn mã riêng lẻ (nghĩ về các đánh giá mã , ví dụ) và một IDE sẽ không giúp đỡ.

Dòng dưới cùng: sử dụng autokhi nó giúp: tức là các vòng lặp trong các vòng lặp. Đừng sử dụng nó khi nó khiến người đọc phải vật lộn để tìm ra loại.


6

Tôi khá ngạc nhiên khi không ai chỉ ra rằng tự động giúp nếu không có loại rõ ràng. Trong trường hợp này, bạn có thể khắc phục sự cố này bằng cách sử dụng #define hoặc typedef trong mẫu để tìm loại có thể sử dụng thực tế (và điều này đôi khi không tầm thường) hoặc bạn chỉ sử dụng tự động.

Giả sử bạn có một hàm, trả về một cái gì đó với loại dành riêng cho nền tảng:

#ifdef PLATFROM1
__int256 getStuff();
#else //PLATFORM2
__int128 getStuff();
#endif

Sử dụng phù thủy bạn sẽ thích?

#ifdef PLATFORM1
__int256 stuff = getStuff();
#else
__int128 stuff = getStuff();
#endif

hoặc chỉ đơn giản là

auto stuff = getStuff();

Chắc chắn, bạn có thể viết

#define StuffType (...)

cũng ở đâu đó, nhưng không

StuffType stuff = getStuff();

thực sự nói gì thêm về loại x? Nó nói với nó là những gì được trả lại từ đó, nhưng nó chính xác là những gì tự động. Điều này chỉ là dư thừa - 'nội dung' được viết 3 lần ở đây - theo ý kiến ​​của tôi làm cho nó ít đọc hơn phiên bản 'tự động'.


5
Cách chính xác để xử lý các loại nền tảng cụ thể là typedefchúng.
cmaster

3

Khả năng đọc là chủ quan; bạn sẽ cần xem xét tình huống và quyết định điều gì là tốt nhất.

Như bạn đã chỉ ra, không có tự động, các khai báo dài có thể tạo ra rất nhiều lộn xộn. Nhưng như bạn cũng đã chỉ ra, các khai báo ngắn có thể loại bỏ thông tin loại có thể có giá trị.

Trên hết, tôi cũng thêm điều này: hãy chắc chắn rằng bạn đang xem khả năng đọc và không thể ghi. Mã dễ viết thường không dễ đọc và ngược lại. Ví dụ, nếu tôi đang viết, tôi thích tự động hơn. Nếu tôi đang đọc, có thể các tuyên bố dài hơn.

Sau đó, có sự nhất quán; điều đó quan trọng với bạn như thế nào Bạn có muốn tự động trong một số phần và khai báo rõ ràng trong phần khác, hoặc một phương thức nhất quán xuyên suốt không?


2

Tôi sẽ lấy điểm của mã ít đọc hơn làm lợi thế và sẽ khuyến khích lập trình viên sử dụng nó ngày càng nhiều hơn. Tại sao? Rõ ràng nếu mã sử dụng auto khó đọc, thì nó cũng sẽ khó viết. Lập trình viên buộc phải sử dụng tên biến có ý nghĩa , để làm cho công việc của mình tốt hơn.
Có thể trong đầu lập trình có thể không viết tên biến có ý nghĩa. Nhưng cuối cùng, trong khi sửa lỗi, hoặc trong đánh giá mã, khi anh ấy / cô ấy phải giải thích mã cho người khác, hoặc trong tương lai không xa, anh ấy / cô ấy giải thích mã để bảo trì mọi người, lập trình viên sẽ nhận ra lỗi và sẽ sử dụng tên biến có ý nghĩa trong tương lai.


2
Tốt nhất, bạn sẽ khiến mọi người viết tên biến như myComplexDerivedTypeđể bù cho kiểu bị thiếu, điều này làm xáo trộn mã theo kiểu lặp lại (mọi nơi sử dụng biến) và điều đó lôi kéo mọi người bỏ qua mục đích của biến trong tên của nó . Kinh nghiệm của tôi là, không có gì là không hiệu quả bằng việc chủ động đặt các trở ngại trong mã.
cmaster

2

Tôi có hai hướng dẫn:

  • Nếu loại biến là rõ ràng, tẻ nhạt để viết hoặc khó xác định sử dụng tự động.

    auto range = 10.0f; // Obvious
    
    for (auto i = collection.cbegin(); i != cbegin(); ++i) // Tedious if collection type
    // is really long
    
    template <typename T> ... T t; auto result = t.get(); // Hard to determine as get()
    // might return various stuff
  • Nếu bạn cần chuyển đổi cụ thể hoặc loại kết quả không rõ ràng và có thể gây nhầm lẫn.

    class B : A {}; A* foo = new B(); // 'Convert'
    
    class Factory { public: int foo(); float bar(); }; int f = foo(); // Not obvious

0

Đúng. Nó làm giảm tính dài dòng nhưng sự hiểu lầm phổ biến là tính dài dòng làm giảm khả năng đọc. Điều này chỉ đúng nếu bạn coi khả năng đọc là thẩm mỹ hơn là khả năng thực tế của bạn đối với mã intepret - vốn không được tăng lên bằng cách sử dụng tự động. Trong ví dụ thường được trích dẫn nhất, các trình lặp vector, nó có thể xuất hiện trên bề mặt sử dụng tự động làm tăng khả năng đọc mã của bạn. Mặt khác, bạn không phải lúc nào cũng biết từ khóa tự động sẽ cung cấp cho bạn những gì. Bạn phải đi theo con đường logic giống như trình biên dịch thực hiện để tái cấu trúc nội bộ đó, và rất nhiều thời gian, đặc biệt với các trình vòng lặp, bạn sẽ đưa ra các giả định sai.

Vào cuối ngày, 'tự động' hy sinh khả năng đọc mã và độ rõ ràng, vì cú pháp và tính thẩm mỹ 'sạch sẽ' (điều này chỉ cần thiết bởi vì các trình lặp có cú pháp phức tạp không cần thiết) và khả năng có thể gõ ít hơn 10 ký tự trên bất kỳ dòng nào. Nó không đáng để mạo hiểm, hoặc nỗ lực liên quan lâu dài.

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.