Phủ định kép trong C ++


124

Tôi vừa tham gia một dự án với cơ sở mã khá lớn.

Tôi chủ yếu giao dịch với C ++ và rất nhiều mã họ viết sử dụng phủ định kép cho logic boolean của họ.

 if (!!variable && (!!api.lookup("some-string"))) {
       do_some_stuff();
 }                                   

Tôi biết những người này là những lập trình viên thông minh, rõ ràng họ không làm điều này một cách tình cờ.

Tôi không phải là chuyên gia C ++ dày dạn, tôi chỉ đoán tại sao họ làm điều này là họ muốn làm cho hoàn toàn tích cực rằng giá trị được đánh giá là đại diện boolean thực tế. Vì vậy, họ phủ nhận nó, sau đó phủ nhận điều đó một lần nữa để đưa nó trở lại giá trị boolean thực tế của nó.

Điều này có đúng không, hay tôi đang thiếu thứ gì đó?



Chủ đề này đã được thảo luận ở đây .
Dima

Câu trả lời:


121

Đó là một mẹo để chuyển đổi sang bool.


19
Tôi nghĩ rằng việc sử dụng nó một cách rõ ràng với (bool) sẽ rõ ràng hơn, tại sao lại sử dụng mánh khóe này !!, vì nó ít gõ hơn?
Baiyan Huang

27
Tuy nhiên, nó là vô nghĩa trong C ++ hoặc C hiện đại hoặc khi kết quả chỉ được sử dụng trong biểu thức boolean (như trong câu hỏi). Nó rất hữu ích khi chúng tôi không có boolloại, để giúp tránh lưu trữ các giá trị khác 10trong các biến boolean.
Mike Seymour

6
@lzprgmr: diễn viên rõ ràng gây ra "cảnh báo hiệu suất" trên MSVC. Sử dụng !!hoặc !=0giải quyết vấn đề, và trong số hai tôi tìm thấy trình dọn dẹp trước đây (vì nó sẽ hoạt động với số lượng lớn hơn các loại). Ngoài ra tôi đồng ý rằng không có lý do để sử dụng hoặc trong mã được đề cập.
Yakov Galka

6
@Noldorin, tôi nghĩ rằng nó cải thiện khả năng đọc - nếu bạn biết ý nghĩa của nó, nó đơn giản, gọn gàng và logic.
jwg

19
Cải thiện? Địa ngục đẫm máu ... cho tôi một số thứ bạn đang hút thuốc.
Noldorin

73

Nó thực sự là một thành ngữ rất hữu ích trong một số bối cảnh. Lấy các macro này (ví dụ từ nhân Linux). Đối với GCC, chúng được triển khai như sau:

#define likely(cond)   (__builtin_expect(!!(cond), 1))
#define unlikely(cond) (__builtin_expect(!!(cond), 0))

Tại sao họ phải làm điều này? GCC __builtin_expectxử lý các tham số của nó như longkhông và booldo đó, cần có một số hình thức chuyển đổi. Vì họ không biết những gì condkhi họ viết các macro đó, nên việc sử dụng !!thành ngữ này là phổ biến nhất .

Họ có thể có thể làm điều tương tự bằng cách so sánh với 0, nhưng theo tôi, thực sự đơn giản hơn để thực hiện phủ định kép, vì đó là cách gần nhất với cast-to-bool mà C có.

Mã này cũng có thể được sử dụng trong C ++ ... đó là một mẫu số chung thấp nhất. Nếu có thể, hãy làm những gì hoạt động trong cả C và C ++.


Tôi nghĩ rằng điều này có rất nhiều ý nghĩa khi bạn nghĩ thông qua nó. Tôi chưa đọc mọi câu trả lời, nhưng có vẻ như quy trình chuyển đổi không được chỉ định. Nếu chúng ta có một giá trị cao 2 bit và với giá trị chỉ cao một bit, chúng ta sẽ có giá trị khác không. Việc phủ định giá trị khác không dẫn đến chuyển đổi boolean (sai nếu nó bằng 0, ngược lại). Sau đó phủ nhận một lần nữa dẫn đến một boolean đại diện cho sự thật ban đầu.
Joey Carson

Vì SO sẽ không cho phép tôi cập nhật nhận xét của mình, tôi sẽ thêm một sửa chữa cho lỗi của mình. Việc phủ định một giá trị tích phân dẫn đến chuyển đổi boolean (sai nếu nó khác không, đúng khác).
Joey Carson

51

Các lập trình viên nghĩ rằng nó sẽ chuyển đổi toán hạng thành bool, nhưng vì các toán hạng của && đã được chuyển đổi hoàn toàn thành bool, nên nó hoàn toàn dư thừa.


14
Visual C ++ cho hiệu năng suy yếu trong một số trường hợp mà không có thủ thuật này.
Kirill V. Lyadvinsky

1
Tôi cho rằng tốt nhất là chỉ nên vô hiệu hóa cảnh báo hơn là xử lý các cảnh báo vô dụng trong mã.
Ruslan

Có lẽ họ không nhận ra điều đó. Điều này thực sự có ý nghĩa hoàn hảo trong bối cảnh của một macro, nơi bạn có thể làm việc với các loại dữ liệu tách rời mà bạn không biết. Xem xét các đối tượng với toán tử dấu ngoặc đơn bị quá tải để trả về giá trị tích phân đại diện cho một trường bit.
Joey Carson

12

Đây là một kỹ thuật để tránh viết (biến! = 0) - tức là chuyển đổi từ bất kỳ loại nào sang dạng bool.

Mã IMO như thế này không có chỗ trong các hệ thống cần được duy trì - bởi vì nó không phải là mã có thể đọc được ngay lập tức (do đó câu hỏi ở vị trí đầu tiên).

Mã phải rõ ràng - nếu không, bạn để lại một di sản nợ thời gian cho tương lai - vì cần có thời gian để hiểu một cái gì đó không cần thiết phải bị chia rẽ.


8
Định nghĩa của tôi về một mánh khóe là điều mà không phải ai cũng có thể hiểu được trong lần đọc đầu tiên. Một cái gì đó cần tìm ra là một mẹo. Cũng kinh khủng vì! nhà điều hành có thể bị quá tải ...
Richard Harrison

6
@ orlandu63: typecasting đơn giản là bool(expr): nó làm đúng và mọi người hiểu ý định từ cái nhìn đầu tiên. !!(expr)là một phủ định kép, vô tình chuyển thành bool ... điều này không đơn giản.
Adrien Plisson

12

Vâng, nó là chính xác và không có bạn không thiếu một cái gì đó. !!là một chuyển đổi sang bool. Xem câu hỏi này để thảo luận thêm.


9

Nó phụ bước cảnh báo trình biên dịch. Thử cái này:

int _tmain(int argc, _TCHAR* argv[])
{
    int foo = 5;
    bool bar = foo;
    bool baz = !!foo;
    return 0;
}

Dòng 'bar' tạo ra "buộc giá trị thành bool 'true' hoặc 'false' (cảnh báo hiệu suất)" trên MSVC ++, nhưng dòng 'baz' lén lút.


1
Thường gặp nhất trong API Windows mà không biết về boolloại - mọi thứ được mã hóa dưới dạng 0hoặc 1trong một int.
Đánh dấu tiền chuộc

4

Là nhà điều hành! Quá tải?
Nếu không, có lẽ họ đang làm điều này để chuyển đổi biến thành bool mà không đưa ra cảnh báo. Đây chắc chắn không phải là một cách làm tiêu chuẩn.


4

Các nhà phát triển di sản C không có kiểu Boolean, vì vậy họ thường #define TRUE 1#define FALSE 0và sau đó sử dụng các kiểu dữ liệu số tùy ý để so sánh Boolean. Bây giờ chúng ta cóbool , nhiều trình biên dịch sẽ phát ra cảnh báo khi một số loại bài tập và so sánh nhất định được thực hiện bằng cách sử dụng hỗn hợp các kiểu số và kiểu Boolean. Hai tập quán này cuối cùng sẽ va chạm khi làm việc với mã kế thừa.

Để khắc phục sự cố này, một số nhà phát triển sử dụng danh tính Boolean sau: !num_valuereturn bool trueif num_value == 0; falsenếu không thì. !!num_valuetrả về bool falsenếu num_value == 0; truenếu không thì. Các phủ định duy nhất là đủ để chuyển đổi num_valuesang bool; tuy nhiên, phủ định kép là cần thiết để khôi phục ý nghĩa ban đầu của biểu thức Boolean.

Mẫu này được gọi là một thành ngữ , nghĩa là, một cái gì đó thường được sử dụng bởi những người quen thuộc với ngôn ngữ. Do đó, tôi không thấy nó là một mô hình chống, nhiều như tôi có thể static_cast<bool>(num_value). Các diễn viên rất có thể đưa ra kết quả chính xác, nhưng một số trình biên dịch sau đó phát ra cảnh báo hiệu suất, vì vậy bạn vẫn phải giải quyết điều đó.

Cách khác để giải quyết điều này là để nói (num_value != FALSE). Tôi cũng ổn với điều đó, nhưng tất cả trong tất cả, !!num_valueít dài dòng hơn, có thể rõ ràng hơn và không khó hiểu khi bạn nhìn thấy nó lần thứ hai.


2

!! đã được sử dụng để đối phó với C ++ ban đầu không có kiểu boolean (cũng như C).


Vấn đề mẫu:

Bên trong if(condition), conditioncần phải đánh giá một số loại như double, int, void*, v.v., nhưng không phải boolvì nó chưa tồn tại.

Giả sử một lớp tồn tại int256(một số nguyên 256 bit) và tất cả các chuyển đổi / phôi số nguyên đã bị quá tải.

int256 x = foo();
if (x) ...

Để kiểm tra xem x"đúng" hay khác không, if (x)sẽ chuyển đổi xthành một số nguyên và sau đó đánh giá xem đó intcó phải là không không. Một quá tải điển hình (int) xsẽ chỉ trả lại LSbits của x. if (x)sau đó chỉ kiểm tra LSbits củax .

Nhưng C ++ có !toán tử. Một quá tải !xthường sẽ đánh giá tất cả các bit của x. Vì vậy, để trở lại logic không đảo ngược if (!!x)được sử dụng.

Tham khảo Các phiên bản cũ hơn của C ++ có sử dụng toán tử `int` của một lớp khi đánh giá điều kiện trong câu lệnh` if () `không?


1

Như Marcin đã đề cập, nó có thể có vấn đề nếu quá tải toán tử đang hoạt động. Mặt khác, trong C / C ++, không vấn đề gì ngoại trừ nếu bạn đang thực hiện một trong những điều sau đây:

  • so sánh trực tiếp với true(hoặc trong C một cái gì đó giống như một TRUEmacro), gần như luôn luôn là một ý tưởng tồi. Ví dụ:

    if (api.lookup("some-string") == true) {...}

  • bạn chỉ đơn giản muốn một cái gì đó được chuyển đổi thành một giá trị 0/1 nghiêm ngặt. Trong C ++, việc gán cho một booldi chúc sẽ thực hiện điều này một cách ngầm định (đối với những thứ được chuyển đổi hoàn toàn thành bool). Trong C hoặc nếu bạn đang xử lý một biến không phải là bool, đây là một thành ngữ mà tôi đã thấy, nhưng (some_variable != 0)bản thân tôi thích sự đa dạng hơn.

Tôi nghĩ rằng trong bối cảnh của một biểu thức boolean lớn hơn, nó chỉ đơn giản là làm xáo trộn mọi thứ.


1

Nếu biến là loại đối tượng, nó có thể có a! Toán tử được định nghĩa nhưng không chuyển sang bool (hoặc tệ hơn là chuyển từ ẩn sang int với các ngữ nghĩa khác nhau. Gọi toán tử! hai lần dẫn đến chuyển đổi thành bool hoạt động ngay cả trong các trường hợp lạ.


0

Điều này đúng nhưng, trong C, vô nghĩa ở đây - 'nếu' và '&&' sẽ xử lý biểu thức theo cùng một cách mà không có '!!'.

Lý do để làm điều này trong C ++, tôi cho rằng, đó là '&&' có thể bị quá tải. Nhưng sau đó, vì vậy có thể '!', Vì vậy nó không thực sự đảm bảo bạn nhận được một bool, mà không cần nhìn vào mã cho các loại variableapi.call. Có lẽ ai đó có nhiều kinh nghiệm về C ++ hơn có thể giải thích; có lẽ nó có ý nghĩa như một biện pháp phòng thủ chuyên sâu, không phải là sự bảo đảm.


Trình biên dịch sẽ xử lý các giá trị theo cùng một cách nếu nó chỉ được sử dụng như một toán hạng cho một ifhoặc &&, nhưng việc sử dụng !!có thể giúp ích cho một số trình biên dịch nếu if (!!(number & mask))được thay thế bằng bit triggered = !!(number & mask); if (triggered); trên một số trình biên dịch nhúng với các loại bit, việc gán ví dụ 256 cho một loại bit sẽ mang lại số không. Nếu không có sự !!chuyển đổi rõ ràng an toàn (sao chép ifđiều kiện sang một biến và sau đó phân nhánh) sẽ không an toàn.
supercat

0

Có lẽ các lập trình viên đã nghĩ gì đó như thế này ...

!! myAnswer là boolean. Trong bối cảnh, nó sẽ trở thành boolean, nhưng tôi chỉ thích đập mọi thứ để đảm bảo, bởi vì có một lần, có một lỗi bí ẩn cắn tôi, và bang bang, tôi đã giết nó.


0

Đây có thể là một ví dụ về thủ thuật hai lần , xem Thành ngữ Bool an toàn để biết thêm chi tiết. Ở đây tôi tóm tắt trang đầu tiên của bài viết.

Trong C ++, có một số cách để cung cấp các bài kiểm tra Boolean cho các lớp.

Một cách rõ ràng là operator booltoán tử chuyển đổi.

// operator bool version
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator bool() const { // use bool conversion operator
      return ok_;
    }
  };

Chúng tôi có thể kiểm tra lớp,

Testable test;
  if (test) 
    std::cout << "Yes, test is working!\n";
  else 
    std::cout << "No, test is not working!\n";

Tuy nhiên, opereator boolđược coi là không an toàn vì nó cho phép các hoạt động vô nghĩa như test << 1;hoặc int i=test.

Sử dụng operator!là an toàn hơn bởi vì chúng tôi tránh các vấn đề chuyển đổi ngầm hoặc quá tải.

Việc thực hiện là tầm thường,

bool operator!() const { // use operator!
    return !ok_;
  }

Hai cách thành ngữ để kiểm tra Testableđối tượng là

  Testable test;
  if (!!test) 
    std::cout << "Yes, test is working!\n";
  if (!test2) {
    std::cout << "No, test2 is not working!\n";

Phiên bản đầu tiên if (!!test)là những gì một số người gọi là thủ thuật double-bang .


1
Kể từ C ++ 11, người ta có thể sử dụng explicit operator boolđể ngăn chặn chuyển đổi ngầm thành các loại tích phân khác.
Arne Vogel
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.