std::move = when std::optional should be launched
Tags: cpp
Published: 2024-04-20 05:56:27

A post from my old blog:
https://codenested.blogspot.com/2023/01/stdmove-when-stdoptional-should-be.html
___
Recently I had a chance to take a look at Rust. When returning from a function Rust uses `std::option` to return either class A in success or class B in exception.

And I found out something similar in C++, namely `std::optional`. Most of C++ users argued that "why use  std::optional when we can fully make use of null pointers?" According to C++ Committee, it was due to minimize human errors. Then we can ask one thing: what kind of errors, then?

Let's take a look at the code below:
```cpp
struct Insider {
    /* whatever great data structure */
};

struct anti_memory_leak {
    Insider *insider=nullptr;
    ~anti_memory_leak() {
        if(insider) delete insider;
    }
};
```

There's nothing special in this code. Since the class Insider can be used optionally, it can be allocated to heap. When anti_memory_leak is removed from memory, Insider object will be also removed in destructor so we have means for memory leak. We proved that we can do it without std::optional.

...... Did we?

Then let's investigate the code below:

```cpp
void doSomethingGreat()
{
    anti_memory_leak object1;
    object1.insider=new Insider();

    std::vector<anti_memory_leak> vector1;
    vector1.push_back(std::move(object1));
    vector1.back().insider->value1=20; // CRASH!
}
```
 
This function crashes in the last line. Why? The reason is in the one line above. When you call `vector1.push_back()`, even though you use std::move() object1 is destructed and recreated. And when destructing, the destructor of anti_memory_leak is called, and it surely remove insider from the heap. In other words, `vector1.back().insider` becomes a dangling pointer. It's kind of unfortunate, the result is the same if you use `emplace_back()` instead of `push_back()`. Anyway the application crashes.

Now is the time std::optional should be used. If you declare an object with `std::optional`, the memory is initialized without allocating that optional object, and it is initialized when the optional object is explicitly created. Of course there's a small overhead, but say, it's also same for other similar(?) classes like `std::shared_ptr`. We have raw pointers, but to manage our precious heap more safely, we can automate some of the management so that we can solve potential incidents(including both memory leak and dangling pointer) more easily.

If we refactor the code above using std::optional, it will be like this:
```cpp
struct Insider {
    int value1;
    /* whatever great data structure */
};

struct anti_memory_leak {
    std::optional<Insider> insider;
    ~anti_memory_leak() {
        if(insider) delete insider;
    }
};

void doSomethingGreat()
{
    anti_memory_leak object1;
    object1.insider=Insider();

    std::vector<anti_memory_leak> vector1;
    vector1.push_back(std::move(object1));
    vector1.back().insider.value1=20; // OK
}
```
If the flow of the code is simple it won't be a big problem. However, if the flow becomes anyway compilcated(e.g. multithreading), there should be chances to free memory or miss the chance when we have to, regardless of my intention. Let's think of std::optional as some kind of insurance policy; though we all agree that insurance fee is somewhat "waste of money"(lol), but we spend money to prepare for the worst? I think it's the same for `std::optional`.