如何释放 boost::shared_ptr 中的指针?

41

boost::shared_ptr能否释放存储的指针而不删除它?

我在文档中没有看到release函数的存在,FAQ中也解释了为什么它不提供release函数,类似于不能对不唯一的指针执行release操作。我的指针是唯一的。如何释放我的指针? 或者使用哪个boost智能指针类可以允许我释放指针? 希望你不会说使用auto_ptr :)


1
为什么不使用auto_ptr?如果它们是唯一的,那么就意味着它们永远不会被复制(因为如果只是暂时存在,多个引用将存在),那么auto_ptr应该可以正常工作。或者,如果您不打算使用智能指针提供的生命周期管理,那么可以使用原始指针。 - jalf
1
除了引用计数语义,shared_ptr还提供了自定义删除器功能,而auto_ptr则没有。因此,这里有一个场景:您使用自定义分配器(即非全局new/delete)创建一个对象,并且在配置对象时需要智能指针以确保异常安全性,但是一旦完成可能会抛出异常的操作后,您需要返回原始指针。不幸的是,auto_ptr或任何boost smart_ptrs似乎都不支持此功能。 - Trevor Robinson
2
遇到了第三方接口的问题。有些接口从工厂返回一个“unique”shared_ptr,因为在C++11之前这可能是最好的方法。抛出shared_ptr -> unique_ptr转换可能会很有用,当你真的想打破规则时,不能打破规则真的很痛苦! - Zero
相关链接:https://dev59.com/cojca4cB1Zd3GeqPx4n9#28922647 - Mohit Jain
14个回答

31

不要这样做。Boost的FAQ条目:

Q. 为什么shared_ptr没有提供release()函数?

A. shared_ptr不能放弃所有权,除非它是unique(),因为其他的副本仍然会销毁对象。

考虑以下情况:

shared_ptr<int> a(new int);
shared_ptr<int> b(a); // a.use_count() == b.use_count() == 2

int * p = a.release();

// Who owns p now? b will still call delete on it in its destructor.
此外,release() 返回的指针很难可靠地释放,因为源 shared_ptr 可能已使用自定义析构器创建。

因此,在以下情况下这样做是安全的:它是指向对象的唯一 shared_ptr 实例(unique() 返回 true),并且该对象不需要特殊的析构器。但如果您使用了这样的 .release() 函数,我仍然会质疑您的设计。


5
理想情况下,只有当 use_count() == 1 时,才能成功释放。这样就可以实现。:| - Macke

24
你可以使用虚拟删除器。这样指针实际上不会被删除。
struct NullDeleter {template<typename T> void operator()(T*) {} };

// pp of type some_t defined somewhere
boost::shared_ptr<some_t> x(pp, NullDeleter() );

7
使用shared_ptr的目的是什么?这很有趣。 - UncleBens
2
@UncleBens,这样的"deleter"(删除器)用途非常有限。例如,当你需要调用一个函数并将类似shared_ptr<some_t>( this )的东西传递给它时。只有使用NullDeleter才能确保安全。 - Kirill V. Lyadvinsky
1
是的,当接口(我们无法控制)需要 shared_ptr 并保证它不会在函数调用后存储指针时,我们使用类似这样的东西。 - McBeth
Magnus在这里提供了一个完整的解决方案。 - Martin Ba
1
如果您可以使用空删除器,那么您可能不需要 smart_ptr - curiousguy

7
孩子们,不要在家尝试以下操作:
// set smarty to point to nothing
// returns old(smarty.get())
// caller is responsible for the returned pointer (careful)
template <typename T>
T* release (shared_ptr<T>& smarty) {
    // sanity check:
    assert (smarty.unique());
    // only one owner (please don't play games with weak_ptr in another thread)
    // would want to check the total count (shared+weak) here

    // save the pointer:
    T *raw = &*smarty;
    // at this point smarty owns raw, can't return it

    try {
        // an exception here would be quite unpleasant

        // now smash smarty:
        new (&smarty) shared_ptr<T> ();
        // REALLY: don't do it!
        // the behaviour is not defined!
        // in practice: at least a memory leak!
    } catch (...) {
        // there is no shared_ptr<T> in smarty zombie now
        // can't fix it at this point:
        // the only fix would be to retry, and it would probably throw again
        // sorry, can't do anything
        abort ();
    }
    // smarty is a fresh shared_ptr<T> that doesn't own raw

    // at this point, nobody owns raw, can return it
    return raw;
}

现在,有没有办法检查引用计数的所有者总数是否> 1?


@curiousguy:它就像是一个“弱引用”,其内存管理由shared_ptr承担。它不会增加也不会减少引用计数,如果您尝试通过weak_ptr使用已删除的对象,则会抛出异常。 - BlueTrin
2
提供答案给这个问题加1分,可惜没有更好的方式。 - Zero
@Zero 是的,这很遗憾,因为它将是一个有用的功能。向Boost和C++委员会投诉吧! - curiousguy
1
让我指出这个解决方案存在的两个严重问题:1> 如果你使用了 make_shared,那么 release 将不会返回一个可用于 delete 的指针,因为 make_shared 分配内存的方式不同。2> weak_ptr 通常使用订阅模式,如果你没有运行 shared_ptr 析构函数,则 weak_ptrlock() 方法将返回一个拥有该内存的有效 shared_ptr。然而,由于引用计数现在加一了,它可能不会删除它。 - Fozi
1
甚至还有这样的说法:“对于具有非平凡析构函数的类类型对象,程序在重用或释放对象占用的存储之前不需要显式调用析构函数;但是,如果没有显式调用析构函数或者没有使用delete表达式释放存储,则析构函数不应该被隐式调用,并且任何依赖于析构函数产生的副作用的程序都具有未定义行为。”(引自http://eel.is/c++draft/basic.life#5) - curiousguy
显示剩余7条评论

6
你需要使用一个删除器,可以请求不要删除底层指针。
请参见此答案(已标记为此问题的重复),以获取更多信息。

4
为了使指针重新指向空,您可以调用shared_ptr::reset()
然而,当您的指针是对象的最后一个引用时,这将删除所指向的对象。不过,这正是智能指针的期望行为。
如果您只需要一个不持有对象的引用,您可以创建一个 boost::weak_ptr(请参见boost文档)。weak_ptr持有对对象的引用,但不会增加引用计数,因此仅存在弱引用时对象将被删除。

3
分享的基础是信任。如果您的程序中的某个实例需要释放原始指针,那么使用`shared_ptr`几乎肯定是错误的类型选择。
然而,最近我也想这样做,因为我需要从不同的进程堆中释放内存。最终,我被告知早些时候使用`std::shared_ptr`的决定并不明智。
我只是惯例性地在清理时使用了这种类型。但是指针只是在几个地方进行了重复复制。实际上,我需要一个`std::unique_ptr`,它(令人惊讶)有一个`release`函数。

2

原谅他们,因为他们不知道自己在做什么。 这个例子可以使用boost::shared_ptr和msvs std::shared_ptr而不会出现内存泄漏!

template <template <typename> class TSharedPtr, typename Type>
Type * release_shared(TSharedPtr<Type> & ptr)
{
    //! this struct mimics the data of std:shared_ptr ( or boost::shared_ptr )
    struct SharedVoidPtr
    {
        struct RefCounter
        {
            long _Uses;
            long _Weaks;
        };

        void * ptr;
        RefCounter * refC;

        SharedVoidPtr()
        {
            ptr = refC = nullptr;
        }

        ~SharedVoidPtr()
        {
            delete refC;
        }
    };

    assert( ptr.unique() );

    Type * t = ptr.get();

    SharedVoidPtr sp; // create dummy shared_ptr
    TSharedPtr<Type> * spPtr = (TSharedPtr<Type>*)( &sp );
    spPtr->swap(ptr); // swap the contents

    ptr.reset();
    // now the xxx::shared_ptr is empy and
    // SharedVoidPtr releases the raw poiter but deletes the underlying counter data
    return t;
}

1

您可以删除共享指针,这对我来说似乎差不多。如果指针始终是唯一的,则std::auto_ptr<>是一个不错的选择。请注意,由于对它们的操作会进行大量复制和临时复制,因此无法在STL容器中使用唯一指针。


@curiousguy:在这种情况下,我对“release”的语义有点模糊,更不用说对OP真正想要做什么也很模糊。 - David Thornley
简单来说,他已经将一个指针转换为share_ptr(使用某个参数p构造了一个share_ptr),现在他想要执行相反的操作(撤销share_ptr的构造)。 - curiousguy

1

我不完全确定您的问题是否是关于实现这一点,但如果您想要一个行为类似于 shared_ptr 的指针,在释放一个 shared_ptr 中的值时,所有指向相同值的其他共享指针都变为 nullptr,则可以将 unique_ptr 放入 shared_ptr 中以实现该行为。

void print(std::string name, std::shared_ptr<std::unique_ptr<int>>& ptr)
{
    if(ptr == nullptr || *ptr == nullptr)
    {
        std::cout << name << " points to nullptr" << std::endl;
    }
    else
    {
        std::cout << name << " points to value " << *(*ptr) << std::endl;
    }
}

int main()
{
    std::shared_ptr<std::unique_ptr<int>> original;
    original = std::make_shared<std::unique_ptr<int>>(std::make_unique<int>(50));

    std::shared_ptr<std::unique_ptr<int>> shared_original = original;

    std::shared_ptr<std::unique_ptr<int>> thief = nullptr;

    print(std::string("original"), original);
    print(std::string("shared_original"), shared_original);
    print(std::string("thief"), thief);

    thief = std::make_shared<std::unique_ptr<int>>(original->release());

    print(std::string("original"), original);
    print(std::string("shared_original"), shared_original);
    print(std::string("thief"), thief);

    return 0;
}

输出:

original points to value 50
shared_original points to value 50
thief points to nullptr
original points to nullptr
shared_original points to nullptr
thief points to value 50

这种行为允许您共享一个资源(比如数组),然后在使所有对该资源的共享引用无效之后重新使用该资源。

0

这里有一个可能有效的黑客技巧。除非你真的陷入困境,否则我不建议使用它。

template<typename T>
T * release_shared(std::shared_ptr<T> & shared)
{
    static std::vector<std::shared_ptr<T> > graveyard;
    graveyard.push_back(shared);
    shared.reset();
    return graveyard.back().get();
}

@curiousguy,我不确定你的意思。这是来自cplusplus.com的auto_ptr :: release的定义:“将auto_ptr内部指针设置为null指针(表示它不指向任何对象),而不销毁当前由auto_ptr指向的对象”。此函数执行此操作;reset将指针设置为null,并将指针复制到静态墓地对象中,以防止shared_ptr删除它。 - Mark Ransom
“这个函数执行了那个操作”,但不仅如此。release的契约只是执行指定的操作。将“指针复制到静态墓地”不属于release规范。 - curiousguy
@curiousguy,目前没有文档记录如何防止shared_ptr删除其所指向的对象。我承认我的“hack”是让一个smart_ptr引用一直挂着而不被销毁。这是一个实现细节,在release_shared函数之外是不可见的。 - Mark Ransom
“_to leave a smart_ptr reference hanging around without ever being destroyed_” 我不知道 static 变量永远不会被销毁。 - curiousguy

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