Khởi tạo các biến trong câu lệnh “if”


80

Tôi đọc rằng trong C ++ 17, chúng ta có thể khởi tạo các biến trong các ifcâu lệnh như thế này

if (int length = 2; length == 2)
    //execute something

Thay vì

int length = 2;
if (length == 2)
    //do something

Mặc dù nó ngắn hơn, nó ảnh hưởng đến khả năng đọc mã (đặc biệt là đối với những người không biết tính năng mới này), tôi cho rằng đây là một cách viết mã không tốt cho việc phát triển phần mềm lớn.

Có lợi thế nào khi sử dụng tính năng này ngoài việc làm cho mã ngắn hơn?


38
Ngoài điều phạm vi?
DeiDei

10
Tôi đoán ai đó đã nói vài năm trước "Tôi đọc rằng trong C ++ 11, chúng ta có thể tạo các câu lệnh lambda như thế này (...). Mặc dù nó ngắn hơn, nhưng nó ảnh hưởng đến khả năng đọc mã (đặc biệt đối với những người không biết tính năng mới này), mà tôi cho rằng đó là một phương pháp viết mã không tốt cho việc phát triển phần mềm lớn. "
R2RT

9
Tôi muốn nói rằng nó có cùng độ dài, không ngắn hơn.
user7860670

5
ý kiến ​​thuần túy, do đó không phải là câu trả lời: if (int length = 2; length == 2)có thể bạn sẽ ngạc nhiên khi nhìn thấy nó lần đầu tiên, nhưng nó không có gì phức tạp mà người ta không thể hiểu được nên đã đến lần thứ hai nó sẽ không còn là một bất ngờ lớn nữa và tuyên bố những thứ trong phạm vi mà nó thuộc một trong những yếu tố chính góp phần vào khả năng đọc. Imho tiền đề của bạn là sai;)
large_prime_is_463035818

14
Lo lắng về khả năng đọc mã đối với những người không biết ngôn ngữ mà mã được viết (nghĩa là "không biết tính năng mới này") là một cuộc chạy đua đến đáy.

Câu trả lời:


97

Nó giới hạn phạm vi lengthđến ifmột mình. Vì vậy, bạn sẽ nhận được những lợi ích giống như chúng tôi ban đầu khi chúng tôi được phép viết

for(int i = 0; i < ... ; ++i) {
   // ...
}

Thay vì biến rò rỉ

int i;
for(i = 0; i < ... ; ++i) {
   // ...
}

Các biến có tuổi thọ ngắn tốt hơn vì một số lý do. Nhưng để đặt tên cho một cặp vợ chồng:

  1. Một cái gì đó có tuổi thọ càng ngắn thì bạn càng cần ít điều cần lưu ý khi đọc những dòng mã không liên quan. Nếu ikhông tồn tại bên ngoài vòng lặp hoặc ifcâu lệnh, thì chúng ta không cần quan tâm đến giá trị của nó bên ngoài chúng. Chúng ta cũng không cần lo lắng giá trị của nó sẽ tương tác với các phần khác của chương trình nằm ngoài phạm vi dự định của nó (điều này có thể xảy ra nếu iở trên được sử dụng lại trong một vòng lặp khác). Nó làm cho mã dễ theo dõi và suy luận hơn.

  2. Nếu biến giữ một tài nguyên, thì tài nguyên đó hiện được giữ trong khoảng thời gian ngắn nhất có thể. Và điều này không có dấu ngoặc nhọn không liên quan. Nó cũng nói rõ rằng tài nguyên liên quan đến ifmột mình. Hãy coi đây là một ví dụ thúc đẩy

    if(std::lock_guard _(mtx); guarded_thing.is_ready()) {
    }
    

Nếu đồng nghiệp của bạn không biết về tính năng này, hãy dạy họ! Việc xoa dịu các lập trình viên không muốn học là một lý do tồi để tránh các tính năng.


12
Tôi sẽ lấy câu cuối cùng này và tát nó trên một tấm áp phích dài hai mét.
Quentin

3
Ngoài ra, các biến tồn tại trong thời gian ngắn (phạm vi chặt chẽ) sẽ giảm thiểu lỗi vì bạn không thể vô tình sử dụng lại một biến sau này trong mã khi mục đích của nó được phục vụ.
Galik

1
Hoặc với một con trỏ yếu:if (auto p = ptr.lock(); p && p->foo()) bar(*p);
Deduplicator

1
3. Trình biên dịch được phép sử dụng lại không gian ngăn xếp trong nhiều trường hợp hơn. (Nếu bạn đã bao giờ vượt qua tôi bằng cách tham chiếu hoặc con trỏ đến một chức năng bên ngoài, ví dụ.)
TLW

Câu hỏi tốt hơn có thể là "ưu điểm của điều này hơn là gì {int i = 2; if (i == 2) {...}}" (Lưu ý phạm vi bổ sung.)
TLW

24

Có lợi thế nào khi sử dụng tính năng này ngoài việc làm cho mã ngắn hơn?

Bạn giảm phạm vi biến. Điều này thực sự có ý nghĩa và tăng khả năng đọc, vì nó củng cố vị trí của các số nhận dạng mà bạn cần suy luận. Tôi đồng ý rằng ifnên tránh các câu lệnh dài dòng bên trong các câu lệnh, nhưng đối với những câu ngắn gọn, thì tốt.

Lưu ý rằng bạn đã có thể thực hiện khởi tạo và phân nhánh trên kết quả trong pre-C ++ 17:

int *get(); // returns nullptr under some condition

if (int *ptr = get())
    doStuff();

Điều này tùy thuộc vào ý kiến ​​cá nhân của một người, nhưng bạn có thể xem xét một điều kiện rõ ràng dễ đọc hơn:

if (int *ptr = get(); ptr != nullptr)
    doStuff();

Bên cạnh đó, lập luận chống lại khả năng đọc của một tính năng bằng cách đề cập đến thực tế là mọi người không quen với nó là nguy hiểm. Mọi người đã không quen với những con trỏ thông minh vào một thời điểm nào đó, nhưng ngày nay tất cả chúng ta vẫn đồng ý (tôi đoán) rằng đó là một điều tốt khi họ ở đó.


4
bạn có thể sử dụng if (auto p =get ())kể từ khi bool điều hành được xác định
sudo rm -rf giảm

19

Dạng mới của câu lệnh if có nhiều cách sử dụng.

Hiện tại, trình khởi tạo hoặc được khai báo trước câu lệnh và bị rò rỉ vào phạm vi môi trường xung quanh hoặc một phạm vi rõ ràng được sử dụng. Với biểu mẫu mới, mã như vậy có thể được viết gọn gàng hơn và việc kiểm soát phạm vi được cải thiện làm cho một số cấu trúc dễ xảy ra lỗi trở nên mạnh mẽ hơn một chút.

Mở Đề xuất Chuẩn cho câu lệnh If với trình khởi tạo

nhập mô tả hình ảnh ở đây

Vì vậy, tóm lại, câu lệnh này đơn giản hóa các mẫu mã phổ biến và giúp người dùng giữ phạm vi chặt chẽ.

Tôi hy vọng nó sẽ giúp!


Bạn có thể vui lòng nói rõ rằng bạn đang trích dẫn từ đề xuất không? Đặc biệt là đoạn thứ hai. Tôi đề xuất dấu ngoặc kép.
Người kể chuyện - Unslander Monica

Cảm ơn @StoryTeller, Vâng, tôi đã trích dẫn đoạn thứ hai từ đề xuất của open-std được kết hợp trong C ++ 17.
Abhishek Sinha

10

Vì lợi ích của việc giảm thiểu phạm vi của các biến, có một thành ngữ chỉ định nghĩa tài nguyên nếu nó hợp lệ khi tạo (ví dụ: các đối tượng dòng tệp ):

if(auto file = std::ifstream("filename"))
{
    // use file here
}
else
{
    // complain about errors here
}

// The identifier `file` does not pollute the wider scope

Đôi khi bạn muốn có thể đảo ngược logic của thử nghiệm đó để biến lỗi thành mệnh đề chính và tài nguyên hợp lệ thành elsemệnh đề. Điều này trước đây không thể thực hiện được. Nhưng bây giờ chúng ta có thể làm:

if(auto file = std::ifstream("filename"); !file)
{
    // complain about errors here
}
else
{
    // use file here
}

Một ví dụ có thể đưa ra một ngoại lệ:

if(auto file = std::ifstream(filename); !file)
    throw std::runtime_error(std::strerror(errno));
else
{
    // use file here
}

Một số người thích viết mã để một hàm sẽ hủy bỏ sớm khi có lỗi và nếu không sẽ tiến triển. Thành ngữ này đặt logic hủy bỏ về mặt vật lý lên trên logic tiếp diễn mà một số người có thể thấy tự nhiên hơn.


8

Nó đặc biệt hữu ích cho các sự kiện logic. Hãy xem xét ví dụ này:

char op = '-';
if (op != '-' && op != '+' && op != '*' && op != '/') {
    std::cerr << "bad stuff\n";
}

Có vẻ hơi thô. Trừ khi bạn đã quá quen thuộc với OR, ANDcác phủ định, bạn có thể cần phải tạm dừng và suy nghĩ về logic này - thường là thiết kế kém. Với if-initializationbạn có thể thêm biểu cảm.

char op = '-';
if (bool op_valid = (op == '-') || (op == '+') || (op == '*') || (op == '/'); !op_valid) {
    std::cerr << "bad stuff\n";
} 

biến được đặt tên cũng có thể được sử dụng lại bên trong if. Ví dụ:

if (double distance = std::sqrt(a * a + b * b); distance < 0.5){
    std::cerr << distance << " is too small\n";
}

Điều này thật tuyệt vời, đặc biệt là biến được xác định phạm vi và do đó không gây ô nhiễm không gian sau đó.


2
Tôi nhận ra đó là chủ quan, nhưng tôi thực sự thích phiên bản "thô" của bạn hơn phiên bản có if-khởi tạo. Tôi thấy nó dễ đọc và dễ hiểu hơn.
Fabio nói Hãy phục hồi Monica vào

@FabioTurati Tôi cho rằng đó là vì bạn đã rất quen thuộc với nó và phiên bản khác là mới. Nhưng theo thời gian, tôi mong đợi trình khởi tạo if sẽ chơi tốt hơn bất kỳ thứ gì tương tự.
Stack Danny

7

Đây là phần mở rộng của một tính năng hiện có, hỗ trợ khả năng đọc theo kinh nghiệm của tôi.

if (auto* ptr = get_something()) {
}

Ở đây, cả hai chúng tôi đều tạo biến ptrvà chúng tôi kiểm tra xem nó có phải là giá trị không. Phạm vi của ptrđược giới hạn ở nơi nó có hiệu lực. Thật dễ dàng hơn rất nhiều để thuyết phục bản thân rằng mọi việc sử dụng ptrđều hợp lệ.

Nhưng điều gì sẽ xảy ra nếu chúng ta đang nói về một thứ gì đó không chuyển đổi booltheo cách đó?

if (auto itr = find(bob)) {
}

Điều đó không hiệu quả. Nhưng với tính năng mới này, chúng tôi có thể:

if (auto itr = find(bob); itr != end()) {
}

Thêm một mệnh đề nói "khi nào khởi tạo này hợp lệ".

Về bản chất, điều này cung cấp cho chúng ta một tập hợp các mã thông báo có nghĩa là "khởi tạo một số biểu thức và khi nó hợp lệ, hãy thực hiện một số mã. Khi nó không hợp lệ, hãy loại bỏ nó."

Việc thực hiện thủ thuật kiểm tra con trỏ từ C ++ 98 đã trở nên thành ngữ. Khi bạn đã nắm bắt được điều đó, phần mở rộng này là đương nhiên.

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.