There's an important difference between "destroy" and "delete" here. The C++ language guarantees many objects are automatically destroyed at a certain point, ending the object's "lifetime". The deletion done by a delete ptr or delete[] ptr expression is different, and is never done automatically by the language.
"Destroy" here just means to end an object's lifetime, plus do automatic cleanup for class types (which includes types defined with class, struct, or union):
- Destroying an object with class type invokes the class destructor.
- Destroying a raw array of objects with class type, or array of array of class objects, etc., invokes the class destructor for each class object.
- "Destroying" any other type, like an
int or std::string* object, does nothing beyond ending the object lifetime.
Every C++ object needs memory storage(1), which is valid for part of the program execution. An object also has one of four kinds of "storage duration" affecting the validity of that memory and the lifetime of the object:
- Automatic storage duration is used for most function-local variables (not marked
extern or static). The language handles providing memory for the object for at least its lifetime. The object is associated with a {compound statement}. The object is initialized when execution reaches the definition statement, and is destroyed when execution leaves the compound statement, whether by return, break, continue, throw, or even goto.
- Static storage duration is used for variables whose lifetime goes up until (nearly) the end of the program. This includes global variables, other namespace scope variables,
static class data members, and function-local variables defined static. The language handles providing memory valid for the entire program execution. The object is destroyed when the program exits normally, e.g. by std::exit or by returning from main.
- Thread storage duration is used for variables declared
static thread_local. This acts very much like static storage duration, but for a thread instead of for the program.
- Dynamic storage duration is used for objects created by a
new expression. Memory is allocated and the object is initialized when the new is evaluated. The object is destroyed and the memory is deallocated only when (and if) a correct delete expression is evaluated.
The point here is that for an object with dynamic storage duration, the program is entirely responsible for when the object's lifetime will end and its memory deallocated. For any other object, the C++ language is responsible for both of these things, and there's little a program can do about that.
So the rule to avoid memory leaks for dynamic storage duration is "one new evaluated, one delete evaluated". (There are other things to watch out for with new and delete to keep everything valid. I won't get into the other rules here.)
In your first example func, str has automatic storage duration. So no delete expression is needed, and inserting one would not be valid since the pointer did not come from a new expression. So the lifetime of str ends at the return, and using the returned pointer value won't be valid.
In your second example oil_leak, the new expression allocates memory for and initializes an unnamed object of type double with dynamic storage duration. Then pointer is initialized with a pointer to that object. When oil_leak returns, the variable pointer is destroyed, but destroying a raw pointer variable does nothing to the object it points at. The object of type double and its memory still exist. But since the program no longer has any pointers to that object or ways to get such a pointer value, it's not possible to ever do a delete to free up that memory.
Correctly using new and delete can be pretty tricky. Luckily, they are almost never needed in modern C++. The basic uses of dynamic storage can nearly always be covered by std::unique_ptr, std::shared_ptr, and the container classes like std::vector, std::string, etc. Fancier uses of "placement new" techniques (mixing up the relationships between the memory and lifetime of objects) can nearly always be covered by std::optional and std::variant. All of these typically use new and delete in their own implementations, but wrap around those details in a way which is easier to use and avoids many ways of accidentally getting it wrong.
Footnote 1: A C++ variable might end up not using any physical memory bytes at all in an executed program, if the compiler can optimize that memory away. But the compiler needs to act "as if" it did occupy memory if there's any way the program could tell the difference.