如何使std::shared_ptr不调用delete()函数

42

我有一些函数需要使用std::shared_ptr作为参数,因此我被迫使用std::shared_ptr,但是我要传递给函数的对象并非动态分配。我该如何将对象包装在std::shared_ptr中,并使std::shared_ptr不调用其上的delete操作。


2
@KenLi 除了 std::shared_ptrboost::shared_ptr 几乎是相同的。 - Angew is no longer proud of SO
2
boost::shared_ptr 基本上是 std::shared_ptr 的原型。 - Will Dean
11
这是一个可怕的想法。只有当函数打算存储指向对象的引用(即共享所有权)时,才应该接受shared_ptr。在某个地方存储该引用时使该对象失效将会带来不好的后果。如果该函数不存储引用,则不应接受拥有指针类型。 - Casey
6
这种情况的一个应用场景是当你有像 FILE * 这样的东西,这种类型可以通过 fopen 等方式进行管理,也可以不进行管理(例如 stdin),而且你有一个类使用了这个对象,超出了创建它的调用(例如创建解析器或记录器)。在这种情况下,你将希望使用“不执行任何操作”的删除器来传递 stdin,但对于已经通过 fopen 打开的文件,则需要使用 fclose 删除器。 - reece
4
我曾经遇到一个实际应用场景,其中这个想法很有意义,它涉及到一个 shared_ptr<ostream>。我可能有一个函数,它接收一个 ostream 并将其作为 shared pointer 存储以备后用。现在假设我想输出到 std::cout。我无法删除 std::cout,但我可以保证它的存在至少与我的函数保持 shared pointer 的时间一样长。 - Cort Ammon
显示剩余3条评论
6个回答

51
MyType t;
nasty_function(std::shared_ptr<MyType>(&t, [](MyType*){}));

2
@Angew:并非巧合,只是简单的复制粘贴。展示了一种更符合C++11的替代方案。 - ronag
8
注意:任何你传递 shared_ptr 给的函数,都可能会接管该对象的所有权,因此原作者需要确保要么(a)这种情况不会发生,要么(b)该对象的生存期比拥有其共享所有权的一切内容的生存期更长。 - Nevin

33

在创建共享指针时,可以指定一个空操作的删除器,例如:

void null_deleter(MyType *) {}

int main()
{
  MyType t;
  nasty_function(std::shared_ptr<MyType>(&t, &null_deleter));
}

26
最好的方法是使用别名构造函数:
nasty_function(std::shared_ptr<MyType>(std::shared_ptr<MyType>{}, &t));

与空删除器方法相比,这种方法不需要分配控制块,并且是noexcept的。
正如@Casey@Nevin所指出的那样,只有在确信函数不会尝试共享所有权,或者对象将超过可能“拥有”它的一切时,才应该使用此方法。

很好的提示。关于版本的说明:这是Cx+11的一个特性,但也可以从boost 1.35中使用nasty_function(boost::shared_ptr<MyType> (boost::shared_ptr<MyType>(), &t)); - radke
1
我只想添加一条关于@T.C使用别名构造函数的注释。如果您使用此技术,则无法在生成的std::shared_ptr上使用std::weak_ptr。 - kctigerhawk
这是否意味着您有一个没有控制块的 shared_ptr - curiousguy

1
Boost.Core提供了一个名为null_deleter的函数对象,专门用于此目的。
引用文档

头文件<boost/core/null_deleter.hpp>定义了boost::null_deleter函数对象,它可以作为智能指针(如unique_ptrshared_ptr)的删除器使用。该删除器在解除分配时不会对所提供的指针执行任何操作,这使得它在指向的对象在其他地方被释放时非常有用。

来自文档的示例:
std::shared_ptr< std::ostream > make_stream()
{
    return std::shared_ptr< std::ostream >(&std::cout, boost::null_deleter());
}

唉,这似乎仍未出现在标准库中... - David V. Corbin

-2

我只是在寻找一个解决方案,看到了这个问题。什么都没找到,于是我自己写了一个很棒的代码。

class HBitmap : public shared_ptr<HBITMAP__>
{
public:
    HBitmap();
    HBitmap(HBITMAP Handle);
    bool autoDelete = true;
};

Win32::HBitmap::HBitmap(HBITMAP Handle)
: shared_ptr<HBITMAP__>(Handle, [&](HBITMAP hBitmap)  {if(autoDelete)DeleteObject(hBitmap);})
{
}

这个解决方案是lambda表达式和继承的结合体。非常完美,快速。你不能期望更多了。不仅可以设置删除器,而且如果进行一些修改,你还可以使用std::function<void(pointer)>作为自定义删除器。使用lambda表达式,你可以自由地运行并做任何想做的事情。


-3
你可以用这个技巧:
A a;
shared_ptr<A> pa(&a);
foo(pa);
new (&pa) shared_ptr<A>();  // pa "forgets" about a

使用放置 new 来覆盖指针而不调用其析构函数,真是巧妙。纯粹从学术角度来看(因为整个问题都相当糟糕):你有没有想法这是否具有明确定义的行为? - Thomas
@Thomas 当然不行。第二行已经是UB了(因为构造函数上的Requires子句被破坏了);你甚至不需要放置new。 - T.C.
1
此外,忽略其他因素,最起码你会泄漏控制块。 - T.C.

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