How to understand and design functions with enumeration arguments that work in tandem with bitwise operators in C++ and Qt?

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/382140

Domanda

In the C++, there are 6 bitwise operators:

Symbol  Operator
&       bitwise AND
|       bitwise inclusive OR
^       bitwise XOR (eXclusive OR)
<<      left shift
>>      right shift
~        bitwise NOT (one's complement) (unary)

In Qt, I have seen |, ~, and & used fairly often in functions:

file->open(QIODevice::WriteOnly | QIODevice::Truncate);

The source for that function looks like this:

bool QFile::open(OpenMode mode)
{
    Q_D(QFile);
    if (isOpen()) {
        qWarning("QFile::open: File (%s) already open", qPrintable(fileName()));
        return false;
    }
    if (mode & Append)
        mode |= WriteOnly;

    unsetError();
    if ((mode & (ReadOnly | WriteOnly)) == 0) {
        qWarning("QIODevice::open: File access not specified");
        return false;
    }
    if (fileEngine()->open(mode)) {
        QIODevice::open(mode);
        if (mode & Append)
            seek(size());
        return true;
    }
    QFile::FileError err = fileEngine()->error();
    if(err == QFile::UnspecifiedError)
        err = QFile::OpenError;
    d->setError(err, fileEngine()->errorString());
    return false;
}

The source code for the enumeration looks like this:

enum OpenModeFlag {
    NotOpen = 0x0000,
    ReadOnly = 0x0001,
    WriteOnly = 0x0002,
    ReadWrite = ReadOnly | WriteOnly,
    Append = 0x0004,
    Truncate = 0x0008,
    Text = 0x0010,
    Unbuffered = 0x0020,
    NewOnly = 0x0040,
    ExistingOnly = 0x0080
};
Q_DECLARE_FLAGS(OpenMode, OpenModeFlag)

In the book I learned Qt and C++ from, it does not adequately touch upon these design patterns, so I am not even quite sure how I am to read this code, or what extent I should use Bitwise Operators in tandem with my enumerations.

Questions

  1. How exactly is file->open(QIODevice::WriteOnly | QIODevice::Truncate)); evaluated in the first place? Does it evaluate the both enumerations like so:

    QIODevice::WriteOnly > 0x0002 > 0000 0000 0000 0000 0000 0010
    QIODevice::Truncate  > 0x0008 > 0000 0000 0000 0000 0000 1000
                                  | 0000 0000 0000 0000 0000 1010
    

    and run the function like this ?

    file->open(0000 0000 0000 0000 0000 1010); // 10 is not set in enum
    
  2. If this is the case, what is the relevance of the number 10 in binary?

  3. Why are the set numbers all powers of 2?
  4. Why use Hexadecimals for the numbers and not plain integers?
  5. Could every bitwise operator be applied to the function and how would I read it? This would be my rudamentary guess:

    file->open(QIODevice::WriteOnly & QIODevice::Truncate); // Both have to be true?
    file->open(QIODevice::WriteOnly | QIODevice::Truncate); // At least one has to be true?
    file->open(QIODevice::WriteOnly ^ QIODevice::Truncate); // Only one has to be true?
    file->open(QIODevice::WriteOnly ~ QIODevice::Truncate); // WriteOnly has to be true and Truncate has to be false
    file->open(QIODevice::WriteOnly << QIODevice::Truncate);// ??? 
    file->open(QIODevice::WriteOnly >> QIODevice::Truncate);// ???
    
  6. In the function source, I also see things such as |=; What does it do?

  7. How would I read this line of code: if ((mode & (ReadOnly | WriteOnly)) == 0) ?
  8. In more practical terms, what are the circumstances in which I would use each bitwise operator to use in tandem with enumerations?

Thanks.

È stato utile?

Soluzione

Your understanding is pretty good. To answer your questions:

  1. Yes, it the function will receive 0x0000 0000 0000 0000 0000 1010 as an argument.
  2. The significance of the decimal number 10 in binary is that it is composed of 2 "on" bits in positions 1 and 3. There's little reason to think about the number in decimal.
  3. The numbers are powers of 2 so that each one can represent an independent flag without conflicting with the others.
  4. They are written in hexadecimal because it's more compact that binary, but still shows the binary nature of the numbers. Since each is a power of 2, it follows a pattern of 0,1,2,4,8 for each hex position. When you see that, you should immediately recognize it as a set of independent flags.
  5. You can apply any of those operators, but they probably wouldn't do what you are thinking from your description. Using bitwise or is the main intended use, and this is a pattern you'll see in many other code bases. Other patterns of use may come into play when you're passing flags that your function received and you don't want to manually check every possible combination of flags. For example, maybe you want to ensure that you only ever open a file as read-only, but want to keep all the other flags passed into the function. You could do something like flags = flags & ~WriteOnly. That says to perform a bitwise and with the flags and the inverse of WriteOnly. The inverse of WriteOnly has every bit set except for the one that you normally associate with WriteOnly. So this has the effect of saying, "I want everything to stay the same except the WriteOnly flag which I want to always be 0."
  6. This isn't a question, but if you see something like x |= y; it just means the same as x = x | y. It is to bitwise or as += is to addition.
  7. You would read it as "If the mode variable has neither ReadOnly nor WriteOnly set"
  8. Bitwise operators are used with enumerations when the values of the enumerations are powers of 2 that represent independent flags (booleans, essentially). Sometimes instead of making the enumerations be 2n, the enumerations are just n. In these cases, you construct the flag you want by doing x = 1 << flag where flag is n. So for bit 0, flag = 0, for bit 1, flag = 1.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top