5

This is a continuation of my previous post. Since it has already been closed I decided to make a new post. I removed half of the code to make it more readable.

Some of the posts I read:

Smart pointers with SDL

Is it possible to use SDL2 with smart pointers?

Couple of questions about SDL_Window and unique_ptr

class cGraphics
{
public:
    //  Creator functions
    std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> Create_Window(int xWin, int yWin);

    //  ctor & dtor
    cGraphics() : m_Window(nullptr, SDL_DestroyWindow) {}
    cGraphics(int xWin, int yWin);
    ~cGraphics();
private:
    std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> m_Window;

};

cGraphics::cGraphics(int xWin, int yWin)
{
    m_Window = std::move(Create_Window(xWin, yWin));

    if (m_Window == nullptr)
    {
        throw "SDL_Window or SDL_Renderer not ready!";
    }
}

cGraphics::~cGraphics()
{
    IMG_Quit();
    SDL_Quit();
}

std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> cGraphics::Create_Window(int xWin, int yWin)
{
    return std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)>(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, xWin, yWin, SDL_WINDOW_SHOWN), SDL_DestroyWindow);
}

The compiler complains that:

'std::unique_ptr<SDL_Window,void (__cdecl *)(SDL_Window *)>::unique_ptr': no appropriate default constructor available

I understand that this error usually shows up when the compiler cannot locate a default constructor for some of the members. However this is not true as I explicitly declared a default value for the std::unique_ptr.

If the compiler is actually complaining about SDL_Window, which is an incomplete type (a C struct), what can I do about this?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Nicholas Humphrey
  • 1,220
  • 1
  • 16
  • 33
  • 3
    As an aside, consider defining your own deleter manually (`struct Invoke_SDL_DestroyWindow { void operator()(SDL_WINDOW* p) const noexcept { SDL_DestroyWindow(p); } };`) and use that instead of lugging around additional pointers and indirecting through them. – Deduplicator Jun 20 '18 at 21:00
  • 1
    What @Deduplicator says is very true. `std::unique_ptr` with a deleter function pointer is twice the size as one without it. It's a waste of space. You're lugging around an extra pointer that you never change, and it also makes it harder for the compiler to optimize (it might not be able to tell that the deleter function never changes). If it's too much of a pain (IMO it is), write a simple wrapper that creates a deleter from a function pointer: https://godbolt.org/g/sM75NC (C++17, but you can get close to that syntax before it) – Justin Jun 20 '18 at 21:10
  • 2
    @Justin: Or maybe it might be easier just to [add an explicit template-specialization](https://en.cppreference.com/w/cpp/language/extending_std): `template <> inline void std::default_delete::operator()(SDL_Window* p) const { SDL_DestroyWindow(p); }`. http://coliru.stacked-crooked.com/a/91120d518230a816 – Deduplicator Jun 20 '18 at 21:29
  • 2
    @Deduplicator That is on the borderline of what's allowed. I would strongly recommend not doing it. It makes it very easy to make mistakes. Consider how `SDL_Window*` is created by `SDL_CreateWindow`, not via `new SDL_Window;`. Thus, after writing that code, if `std::make_unique(...)` were written, you'd have undefined behavior. Simply put, that's extremely easy to mess up. – Justin Jun 20 '18 at 21:33
  • 1
    @Justin: As `SDL_Window` is incomplete, the call to `std::make_unique` will result in a compilation-error. No danger there. – Deduplicator Jun 20 '18 at 21:35
  • 1
    @Deduplicator Yes, I assumed so. However, if you tried to do that in the general case, it's easy to go wrong. If I saw that code in a code review, I'd be really skeptical. – Justin Jun 20 '18 at 21:37

2 Answers2

8

A std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> is not default constructable. That means in

cGraphics::cGraphics(int xWin, int yWin) ***
{
    m_Window = std::move(Create_Window(xWin, yWin));

    if (m_Window == nullptr)
    {
        throw "SDL_Window or SDL_Renderer not ready!";
    }
}

When you reach the part *** the compiler is going to try and default construct m_Window since you didn't do so in the member initialization list. That attempt from the compiler to default construct m_Window is what causes the error. We can fix this by moving m_Window = std::move(Create_Window(xWin, yWin)); out of the constructor body and putting it intp the member initialization list like

cGraphics::cGraphics(int xWin, int yWin) : m_Window(Create_Window(xWin, yWin))
{   
    if (m_Window == nullptr)
    {
        throw "SDL_Window or SDL_Renderer not ready!";
    }
}

If you don't want to do that then you can delegate to the default constructor and then assign to m_Window like you were doing originally. That would look like

cGraphics::cGraphics(int xWin, int yWin) : cGraphics()
{
    m_Window = Create_Window(xWin, yWin);

    if (m_Window == nullptr)
    {
        throw "SDL_Window or SDL_Renderer not ready!";
    }
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    You do not need `std::move` there - the `CreateWindow` function is already returning a temporary. – SergeyA Jun 20 '18 at 20:44
  • 1
    Aside: the `std::move` is unnecessary in all these bits of code. – aschepler Jun 20 '18 at 20:44
  • Thanks! This definitely solves my problem. The delegation is more elegant I guess, but the key is that I forgot to inform the compiler how to initialize m_Window (I thought I could use the method inside of the ctor which is wrong). – Nicholas Humphrey Jun 20 '18 at 20:46
  • @SergeyA @aschepler Thank you guys, yeah you are right, they return a temp object already so no need to `std::move`. – Nicholas Humphrey Jun 20 '18 at 20:46
  • 1
    @SergeyA I've removed the moves, didn't see they were redundant, thanks. As far as why delegating works, it is because the OP correctly initializes the pointer to `nullptr` in the default constructor. – NathanOliver Jun 20 '18 at 20:47
  • 1
    @NathanOliver, sure I figured it out after I typed the comment (and even was within grace window to remove this evidence of my stupidity!) :) – SergeyA Jun 20 '18 at 20:49
  • 1
    @NicholasHumphrey, as a matter of fact, initializer list is more elegant than delegating constructor. – SergeyA Jun 20 '18 at 20:49
  • @SergeyA Thanks! Good to know. BTW can I ask an additional question? Suppose I put some `std::unique_ptr` of `SDL` objects (e.g. `SDL_Texture`, like `SDL_Window` they are just C structs, so no default ctor) into a container, say `std::unordered_map`, how can I avoid the same error msg? Do I need to wrap the `std::unique_ptr` with another class and write a default ctor? Seems to be too much though. – Nicholas Humphrey Jun 20 '18 at 21:34
  • 1
    @NicholasHumphrey use `insert` or `emplace` to insert elements, `find` for lookup. You won't be able to use subscript operator, unfortunately. You also seem to misunderstand the cause of the problem. It is not that C structs do not have default ctor - the do! It is the `unique_ptr` with deleter which does not have one. – SergeyA Jun 20 '18 at 21:37
  • Thanks for clarifying that, I didn't know C struct also has ctor, my fault~ Yes I'm using `emplace` but get the same complaint (Basically I have a `std::unordered_map> m_TextureMap`, and compiler complains that there is no default ctor for that unique pointer. The only way I can think about is to create a wrapper class for that unique pointer and then create a ctor for it, because I cannot use the same method in this post. Is there a better way? – Nicholas Humphrey Jun 20 '18 at 21:43
  • 1
    @NicholasHumphrey Something like `m_TextureMap.emaplce("some string", function_that_returns_uinique_ptr());` should work. – NathanOliver Jun 20 '18 at 21:45
  • @NathanOliver Thanks. I'm indeed using something similar: `m_TextureMap.emplace(texid, CreateTexture(texres, r, g, b));` in which m_TextureMap is the `std::unordered_map` and `CreateTexture()` returns a unique pointer. I think the error is of similar reason I encountered in my main post? – Nicholas Humphrey Jun 20 '18 at 21:48
  • 1
    @NicholasHumphrey You might be better off asking another question. Without seeing a [mcve] it's hard for me to say what the problem is. – NathanOliver Jun 20 '18 at 21:49
  • Sure I'm going to open another post. :D – Nicholas Humphrey Jun 20 '18 at 21:51
4

Here is how you defined your unique_ptr:

std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> m_Window;

That means, it's constructor has to be called with an instance of the custome deleter, in your case, function SDL_DestroyWindow - remember, you told the pointer what is deleter's type, not what the actual deleter (a function pointer in your case).

To make this code work, you have to properly construct your pointer with a deleter instance, for example:

cGraphics::cGraphics(int xWin, int yWin) : 
                     m_Window{Create_Window(xWin, yWin)} {...}
SergeyA
  • 61,605
  • 5
  • 78
  • 137