- and as much as I disagree with using it _everywhere_ and injecting object-oriented design into everything, RAII is actually really useful for OS resources such as an `SDL_Window*`.
+ but remember the rule of three - if you declare a destructor, you pretty much always also want to declare a copy constructor, and a copy assignment operator
> If a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three.
from [cppreference.com](https://en.cppreference.com/w/cpp/language/rule_of_three#Rule_of_three), retrieved 2024-06-20 21:13 UTC+2
- what will happen with an explicit destructor, but a default copy constructor and copy assignment operator, is that upon copying an instance of the object, the new object will receive the same pointer as the original -
and _its_ destructor will run to delete the pointer, _in addition to_ the destructor that will run to delete our original object - causing a double free!
+ this fulfills the rule of five, which says that if you follow the rule of three and would like the object to be movable, you will want a move constructor and move assignment operator.
- > Because the presence of a user-defined (or `= default` or `= delete` declared) destructor, copy-constructor, or copy-assignment operator prevents implicit definition of the move constructor and the move assignment operator, any class for which move semantics are desirable, has to declare all five special member functions: […]
from [cppreference.com](https://en.cppreference.com/w/cpp/language/rule_of_three#Rule_of_five), retrieved 2024-06-20 21:13 UTC+2
- on the other hand it being this verbose does suggest that _maybe_ it's a little expensive to call, so there's that.
maybe save it somewhere and reuse it during a frame.
I dunno, I'm not your dad to be telling you what to do.
neither have I read the SDL2 source code to know how expensive this function is, but the principle of least surprise tells me it should always return the _current_ window size, so I assume it always asks the OS.
- but the fine folks designing the C++ standard library have already thought of this use case.
this is what _smart pointers_ are for after all - our good friends `std::shared_ptr` and `std::unique_ptr`, which `delete` things for us when they go out of scope, automatically!
- naturally it has to know _how_ to perform the freeing.
the standard library designers could have just assumed that all allocations are created with `new` and deleted with `delete`, but unfortunately the real world is not so simple.
we have C libraries to interface with after all, and there destruction is accomplished simply by calling functions!
+ not to mention polymorphism - `delete` does not have any metadata about the underlying type. it calls the destructor of the _static_ type, which wouldn't work very well if the actual type was something else.
- because of this, `std::shared_ptr` actually stores a _deleter_ object, whose sole task is to destroy the shared pointer's contents once there are no more references to it.
- this is despite `std::shared_ptr`'s extra reference counting semantics -
having formed somem Good Memory Management habits in Rust, I tend to shape my memory layout into a _tree_ rather than a _graph_, so to pass the window to the rest of the program I would pass an `SDL_Window&` down in function arguments.
then only `main` has to concern itself with how the `SDL_Window`'s memory is managed.
- using `std::shared_ptr` does have a downside though, and it's that there is some extra overhead associated with handling the shared pointer's _control block_.
- an additional thing to note is that when you're constructing an `std::shared_ptr` from an existing raw pointer, C++ cannot allocate the control block together with the original allocation.
this can reduce cache locality if the allocator happens to place the control block very far from the allocation we want to manage through the shared pointer.
- an `std::unique_ptr` stores which deleter to use as part of its template arguments - you may have never noticed, but `std::unique_ptr` is defined with an additional `Deleter` argument in its signature:
- unfortunately for us, adding a deleter to an `std::unique_ptr` is not as simple as adding one to an `std::shared_ptr`, because it involves creating an additional type -
we cannot just pass `SDL_DestroyWindow` into that argument, because that's a _function_, not a _type_.
- having to type this whole type out every single time we want to refer to an owned `SDL_Window` is a bit of a pain though, so we can create a type alias:
```cpp
namespace sdl
{
using window = std::unique_ptr<SDL_Window, function_delete<SDL_Window, SDL_DestroyWindow>>;
- and having to repeat `SDL_Window` twice in the type alias is no fun, so we can create a type alias for `std::unique_ptr<T, function_delete<T, Deleter>>` too:
```cpp
template <typename T, void (*Deleter)(T*)>
using c_unique_ptr = std::unique_ptr<T, function_delete<T, Deleter>>;
namespace sdl
{
using window = c_unique_ptr<SDL_Window, SDL_DestroyWindow>;
- the unfortunate downside to this approach is that you can get pretty abysmal template error messages upon type mismatch:
```cpp
void example(const sdl::window& w);
int main(void)
{
example(1);
// ...
}
```
```diagnostics-clang
sdl2.cpp:36:5: error: no matching function for call to 'example'
36 | example(1);
| ^~~~~~~
sdl2.cpp:21:6: note: candidate function not viable: no known conversion from 'int' to 'const sdl::window' (aka 'const unique_ptr<SDL_Window, free_fn<SDL_Window, &SDL_DestroyWindow>>') for 1st argument