能否使用智能指针与SDL2一起使用?

6
我有这一行代码。
//std::unique_ptr<SDL_Window> _window_; // this is somewhere else...
_window_ = std::make_unique<SDL_Window>(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _WIDTH_, _HEIGHT_, SDL_WINDOW_SHOWN));

它会产生以下编译器错误

In file included from /usr/include/c++/6/memory:81:0,
                 from /home/user/prj/src/main.cpp:4:
/usr/include/c++/6/bits/unique_ptr.h: In instantiation of ‘typename 
std::_MakeUniq<_Tp>::__single_object std::make_unique(_Args&& ...) [with _Tp = SDL_Window; _Args = {SDL_Window*}; typename 
std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<SDL_Window>]’:
/home/user/prj/src/main.cpp:36:170:   required from here
/usr/include/c++/6/bits/unique_ptr.h:791:30: error: invalid use of incomplete type ‘struct SDL_Window’
     { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }

为什么要使用智能指针?(即使不使用智能指针也可以正常工作,所以我猜可能是我没有理解语法,很容易修复。下面会提供源代码和CMakeLists.txt文件。) CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
project(prj)

find_package(SDL2 REQUIRED)
include_directories(prj ${SDL2_INCLUDE_DIRS})

add_executable(prj main.cpp)
target_link_libraries(prj ${SDL2_LIBRARIES})

main.cpp

#include "SDL.h"
#include <memory>
#include <iostream>
#include <fstream>
#include <cstdint>

class Window
{

    public:

    Window()
        : _window_{nullptr}
        , _surface_{nullptr}
    {
        if(SDL_Init(SDL_INIT_VIDEO) < 0)
        {
            std::cerr << SDL_GetError() << std::endl;
        }
        else
        {
            _window_ = std::make_unique<SDL_Window>(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _WIDTH_, _HEIGHT_, SDL_WINDOW_SHOWN));

            if(_window_ == nullptr)
            {
                std::cerr << SDL_GetError() << std::endl;
            }
            else
            {
                _surface_ = std::make_unique<SDL_Surface>(SDL_GetWindowSurface(_window_.get()));
                SDL_FillRect(_surface_.get(), nullptr, SDL_MapRGB(_surface_->format, 0xFF, 0xFF, 0xFF));
                SDL_UpdateWindowSurface(_window_.get());
                SDL_Delay(1000);
            }
        }
    }

    ~Window()
    {
        SDL_DestroyWindow(_window_.get());
        SDL_Quit();
    }

    private:

    const int32_t _WIDTH_{600};
    const int32_t _HEIGHT_{400};

    std::unique_ptr<SDL_Window> _window_;
    std::unique_ptr<SDL_Surface> _surface_;

};


int main(int argc, char* argv[])
{
    Window window;
    return 0;
}

@lcs 编辑了问题 - FreelanceConsultant
struct SDLWindowDestroyer { void operator()(SDL_Window* w) { SDL_DestroyWindow(w); } }; using SDLWindowPtr = std::unique_ptr<SDLWindow, SDLWindowDestroyer>; - Daniel Schepler
@user3728501 那个问题的语法已经在答案中了。 - lcs
@lcs他们使用不同的结构体语法。在这种情况下,我的问题就变成了“我的尝试有什么问题”。 - FreelanceConsultant
@lcs 即最长公共子序列。请参见我在问题底部附加的屏幕截图 - 在链接引用中有一个不明显的注释。 - FreelanceConsultant
3个回答

13

解决方案

通过大量的试错,最终找到了答案,现在将在此处解释解决方案。

这是正确的语法:

// first define the unique_ptr as member of class
std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> _window_;

// second, initialize in the member initialization list of class constructor
// probably don't need to do this if not embedding as member of class
class_name()
    : _window_(nullptr, SDL_DestroyWindow)
{
    // blaa blaa SDL code etc
    _window_.reset(SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN));
}

// finally we need to be able to delete
// but this is handled automatically

解释

当我们将unique_ptr作为数据成员添加时,需要同时给出类型SDL_Window和“删除器函数格式/语法”,因为普通的delete调用是不正确的。我们使用decltype自动从删除器函数构造正确的删除器格式。(也许不是最准确的解释。)在某种程度上,decltype有点像auto...

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

这个对象必须被初始化。我们在构造函数中完成此操作。我们将指针设置为nullptr(因为我们不想在初始化SDL2之前初始化它),并设置删除函数。

: _window_(nullptr, SDL_DestroyWindow)

在初始化SDL之后,我们需要创建一个窗口。最简单的方法是调用智能指针reset()函数。我们将其传递给由创建窗口的函数返回的新指针。

_window_.reset(SDL_CreateWindow(...));

完成了。花费了很长时间才理解,但现在有意义了。参考资料

http://en.cppreference.com/w/cpp/memory/unique_ptr

为什么我的unique_ptr认为它有一个空的函数指针deleter?

这个unique_ptr的初始化有什么问题?


你需要一个自定义删除器,但请注意你的代码还可以进一步改进:http://coliru.stacked-crooked.com/a/adc9e7f6bf45e0ca - milleniumbug
请参考链接。使用无状态函数对象可以减小智能指针的大小,并且您不需要将函数传递给std::unique_ptr的构造函数。 - milleniumbug
@milleniumbug 抱歉,我在这里错过了重点(大小缩减)。 - Nikos C.
@NikosC。不行,因为SDL的工作方式如此,如果同一类调用了SDL_Init,那么这个方法就行不通了。 - FreelanceConsultant
你正在将其初始化为 nullptr,所以没问题。(顺便说一句,我只是指成员初始化列表,而不是构造函数体中的 .reset() 调用。显然,那个保持不变。) - Nikos C.
显示剩余4条评论

2
这些结构体是不透明的数据结构(opaque data structures),你没有它们的完整定义。这意味着,默认的std::unique_ptr删除器不知道如何“删除”这些结构体。
你需要提供自己的自定义删除器。对于SDL_Window,它是SDL_DestroyWindow函数。

2
这也意味着在这种情况下不使用 make_unique - Nikos C.

1
一个SDL窗口应该使用SDL_DestroyWindow来销毁,而不是像unique_ptr的默认删除器那样使用delete。你需要为unique_ptr提供一个自定义删除器,它将调用SDL_DestroyWindow。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接