New feature of C++17 -- std::optional

Posted by danielson2k on Sat, 22 Jan 2022 06:42:07 +0100

catalogue

1, Foreword

2, Use of optional

3, Solve the problems in the preface

1, Foreword

We may often encounter this situation in the process of writing code:

A function obtains a return value after a series of calculations, but the function may have abnormal branches during execution. The value to be calculated has not been obtained when returning from these abnormal branches. For example, if we want to calculate an unsigned type, we may return a - 1 in these exception branches to indicate that the calculation fails, or the execution is wrong, or even different negative values are used to indicate different exceptions... Due to the existence of these values, the return value should be of uint32 type. In order to accommodate the possible negative values and the whole range of uint32, the return value becomes int64... Does this int64 mean either an error code or a calculation result? This has caused inconvenience to reading and use. In addition, the emergence of this negative value also introduces devil numbers.

For example, chestnuts:

#include <cstdint>

int64_t getUintValue() {
    if (xxx) {
        return -1; // malloc fail
    }
    if (xxx) {
        return -2; // invalid param
    }
    doSomeWork();
    return 28;
}

Or is it a function that calculates Boolean values and returns false when it fails? The caller was stunned when he got the value..

bool getBoolValue() {
    if (xxx) {
        return false;  // false returned when calculation fails
    }
    doSomeWork();
    return false;  // false returned when the calculation is successful
}

Another situation occurs in enumerations. Usually, when the enumeration type is not known yet, we will add an enumeration value called UNKNOWN. In this way, any place that uses this enumeration type must first judge whether it is UNKNOWN, resulting in code redundancy. For example, chestnuts:

enum Color {
    RED,
    BLUE,
    GREEN,
    UNKNOWN
};

void printColor(Color c) {
    // Don't judge whether it's UNKNOWN here?
    // If you don't judge, what happens when you reuse this function elsewhere?
}

void judgeColor(Color c) {
    if (c != UNKNOWN) {
        printColor(c);
    }
}

C++17 adds std::optional to solve this problem.

2, Use of optional

optional is a template class:

template <class T>
class optional;

It has two internal states, either with a value (type T) or without a value (std::nullopt). It's a bit like a T * pointer, either pointing to a T type or a null pointer (nullptr).

std::optional can be constructed in the following ways:

#include <iostream>
#include <optional>
using namespace std;

int main() {
    optional<int> o1;  //The default initialization is nullopt when nothing is written
    optional<int> o2 = nullopt;  //Initialize to no value
    optional<int> o3 = 10;  //Initialize with a value of type T
    optional<int> o4 = o3;  //Initialize with another option
    return 0;
}

To check whether an optional object has a value, you can directly use if or has_value()

#include <iostream>
#include <optional>
using namespace std;

int main() {
    optional<int> o1;
    if (o1) {
        printf("o1 has value\n");
    }
    if (o1.has_value()) {
        printf("o1 has value\n");
    }
    return 0;
}

When an option has a value, it can be used by using a pointer (* sign and - > sign), or by using value() gets its value:

#include <iostream>
#include <optional>
#include <string>
using namespace std;

int main() {
    optional<int> o1 = 5;
    printf("%d\n", *o1);
    optional<string> o2 = "hello";
    printf("%s\n", o2->c_str());
    printf("%s\n", o2.value().c_str());
    return 0;
}

Change a valuable option to no value with reset(). This function destructs the stored T-type object

#include <iostream>
#include <optional>
using namespace std;

int main() {
    optional<int> o1 = 5;
    o1.reset();
}

3, Solve the problems in the preface

So the three questions in the first chapter can be rewritten as follows:

#include <cstdint>
#include <optional>
using namespace std;

optional<uint32_t> getUintValue() {
    if (xxx) {
        return nullopt; // malloc fail
    }
    if (xxx) {
        return nullopt; // invalid param
    }
    doSomeWork();
    return 28;
}
#include <optional>
using namespace std;

optional<bool> getBoolValue() {
    if (xxx) {
        return nullopt;  // Returns when the calculation fails
    }
    doSomeWork();
    return false;  // false returned when the calculation is successful
}
#include <optional>
using namespace std;

enum Color {
    RED,
    BLUE,
    GREEN
};

void printColor(Color c) {
    // There is no need to judge whether this value is legal
}

void judgeColor(optional<Color> c) {
    if (c.has_value()) {
        printColor(c.value());
    }
}

Readability explosion!

Topics: C++