使用成员函数作为自定义删除器与std::shared_ptr一起存在的问题

46

我正在尝试使用std::shared_ptr和自定义删除器。具体来说,我正在将其与SDL_Surface一起使用,如下所示:

std::shared_ptr<SDL_Surface>(SDL_LoadBMP(....),SDL_FreeSurface);
编译和运行都没有问题。然而,我想尝试自己的删除器,但无法弄清如何操作。SDL_FreeSurface 的文档在这里:http://sdl.beuc.net/sdl.wiki/SDL_FreeSurface。其中我发现 SDL_FreeSurface 声明为:
void SDL_FreeSurface(SDL_Surface* surface);

根据这些信息,我尝试了以下函数作为测试:

void DeleteSurface(SDL_Surface* surface)
{
    std::cout << "Deleting surface\n";
    SDL_FreeSurface(surface);
}

然而,使用g++编译时我遇到了以下错误:

error: no matching function for call to 'std::shared_ptr<SDL_Surface>::shared_ptr(SDL_Surface*, <unresolved overloaded function type>)'
我查看了gcc std :: shared_ptr实现的gnu文档,但无法理解。我做错了什么吗?
编辑:我已经缩小了问题范围,但仍将保留上面的原始问题。我有一个Game类,如果我将其剥离到基本实现,则是这样的:
class Game {
    public:
        /* various functions */
    private:
        void DeleteSurface(SDL_Surface* surface);
        bool CacheImages();
        std::vector<std::shared_ptr<SDL_Surface> > mCachedImages;

        /* various member variables and other functions */
}

使用如上所示的DeleteSurface实现,以及如下所示的CacheImages()实现:

bool CacheImages()
{
    mCachedImages.push_back(std::shared_ptr<SDL_Surface>(SDL_LoadBMP(...),DeleteSurface);
    return true;
}

我在使用游戏时遇到了上述错误。但是,如果我将DeleteSurface()函数移出Game类而不进行其他修改,则代码会编译通过。为什么将DeleteSurface函数包含在Game类中会引起问题?


你的例子在我的电脑上编译通过。 - Kerrek SB
2个回答

63
std::shared_ptr<SDL_Surface>(SDL_LoadBMP(....), [=](SDL_Surface* surface)
{
    std::cout << "Deleting surface\n";
    SDL_FreeSurface(surface);
});
或者
void DeleteSurface(SDL_Surface* surface)
{
    std::cout << "Deleting surface\n";
    SDL_FreeSurface(surface);
}

std::shared_ptr<SDL_Surface>(SDL_LoadBMP(....), DeleteSurface);

编辑:

看到您更新的问题,DeleteSurface 应该是一个非成员函数,否则您需要使用 std::bind 或者 std::mem_fn 或其他成员函数指针适配器。


@Wheels2050:它编译得很好,如果是这种情况,你在示例中漏掉了一些东西。 - ronag
谢谢你的回答。我仍然不明白为什么成员函数无法工作,但你已经向我展示了需要调查的内容。 - Wheels2050
1
@Wheels2050:非静态成员函数需要在“Game”的实例上调用,这将是成员函数内部由“this”指针指向的对象指针。当它调用删除器函数时,“shared_ptr”没有“Game”的实例。 - Steve Jessop
好的,谢谢Steve - 现在你解释了,这很明显!我感谢你的澄清。 - Wheels2050
12
这里实际上并没有捕获任何东西,对吗?你的lambda捕获组中可能不应该有一个=符号,我发现这会让人感到困惑。 - Kyle Strand
显示剩余2条评论

10

这段代码提供了一个使用对象方法作为删除器的共享指针构造示例。它展示了如何使用 std::bind 指令。

该示例是一个简单的对象回收器。当对象的最后一个引用被销毁时,对象将返回到回收器内的空闲对象池。

通过向 get()add() 方法添加键,并将对象存储在 std::map 中,可以轻松地将回收器更改为对象缓存。

class ObjRecycler
{
private:
    std::vector<Obj*> freeObjPool;
public:
    ~ObjRecycler()
    {
        for (auto o: freeObjPool)
            delete o;
    }

    void add(Obj *o)
    {
        if (o)
            freeObjPool.push_back(o);
    }

    std::shared_ptr<Obj> get()
    {
        Obj* o;
        if (freeObjPool.empty())
            o = new Obj();
        else
        {
            o = freeObjPool.back();
            freeObjPool.pop_back();
        }
        return std::shared_ptr<Obj>(o, 
             std::bind(&ObjRecycler::add, this, std::placeholders::_1));
    }
}

3
只想说这个并没有按照预期工作。当你调用 get() 时,你将总是得到一个引用计数为 1 的共享指针。更糟糕的是,多个共享指针可以指向相同的内存。如果其中一个超出作用域,你会得到段错误。你需要的不是一个 std::vector<Obj*>, 而是一个 std::vector<std::weak_ptr<Obj>> - rwols
1
你不想使用 std::vector<std::weak_ptr<Obj>>,因为它不能保留你想要重用的缓冲区的生命周期,你需要使用 std::vector<std::unique_ptr<Obj>>。你可以从现有的 std::unique_ptr 隐式创建一个 std::shared_ptr,然后自定义删除器可以获取原始的未拥有指针并将其放入新的 std::unique_ptr 中,然后将其放入你的 std::vector 中。 - Sam Kellett

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