我想我不完全理解析构函数在C ++中是如何工作的。以下是我为重新创建问题而编写的示例程序:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
struct Odp
{
int id;
Odp(int id)
{
this->id = id;
}
~Odp()
{
cout << "Destructing Odp " << id << endl;
}
};
typedef vector<shared_ptr<Odp>> OdpVec;
bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec)
{
shpoutOdp.reset();
for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++)
{
Odp& odp = *(iter->get());
if (odp.id == id)
{
shpoutOdp.reset(iter->get());
return true;
}
}
return false;
}
int main()
{
OdpVec vec;
vec.push_back(shared_ptr<Odp>(new Odp(0)));
vec.push_back(shared_ptr<Odp>(new Odp(1)));
vec.push_back(shared_ptr<Odp>(new Odp(2)));
shared_ptr<Odp> shOdp;
bool found = findOdpWithID(0, shOdp, vec);
found = findOdpWithID(1, shOdp, vec);
}
在main()
结束之前,该程序的输出是:
Destructing Odp 0
Destructing Odp 1
为什么会这样?我保留对向量中每个Odp
实例的引用。是否与通过引用传递shared_ptr
有关?
更新我认为shared_ptr::reset
根据MSDN减少参考次数:
运营商都减少了 资源的引用计数 目前由* this
拥有
但也许我误解了它?
更新2 :看起来这个版本的findOdpWithID()
不会导致析构函数被调用:
bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec)
{
for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++)
{
Odp& odp = *(iter->get());
if (odp.id == id)
{
shpoutOdp = *iter;
return true;
}
}
return false;
}
答案 0 :(得分:12)
这一行可能就是让你失望的原因。
shpoutOdp.reset(iter->get());
你在这里做的是从智能指针获取(通过get()
)裸指针,该指针上没有任何参考跟踪信息,然后告诉shpoutOdp
将自身重置为指着裸指针。当shpoutOdp
被破坏时,它不知道还有另一个shared_ptr
指向同一个东西,shpoutOdp
继续销毁它所指向的东西。
你应该做的
shpoutOdp = *iter;
将正确保持引用计数。顺便说一句,reset()
确实递减了引用计数器(并且只有在计数达到0时才会销毁)。
答案 1 :(得分:4)
这么多东西几乎都被正确使用了:
bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec)
此处参数shpoutOdp是输入参数的副本。考虑到它是一个共享指针,这并不是一件大事,但这可能不是你想要的。您可能希望通过引用传递,否则为什么首先将它传递给函数。
shpoutOdp.reset();
传入参数时重置参数。
这是否意味着它可能是脏的(那么为什么将它作为输入参数)如果你想传递一些东西,它会使函数返回一个共享指针。
Odp& odp = *(iter->get());
除非你真的需要(并且你真的需要),否则不要使用get on shared指针。提取指针对于获取指针所指向的内容并不是必需的,因为您正在处理指针,因此更容易出错。等效的安全(r)行是:
Odp& odp = *(*iter); // The first * gets a reference to the shared pointer.
// The second star gets a reference to what the shared
//pointer is pointing at
这就是出错的地方:
shpoutOdp.reset(iter->get());
您正在从指针创建新的共享指针。不幸的是,指针已经被另一个共享指针管理了。所以现在你有两个共享指针认为它们拥有指针,并且当它们超出范围时将删除它(第一个指针在函数末尾超出范围,因为它是输入参数的副本(而不是比参考))。正确的做法是做一个任务。然后共享指针知道它们正在共享一个指针:
shpoutOdp = *iter; // * converts the iterator into a shared pointer reference
下一行虽然并非完全错误,但假设使用的迭代器是随机访问(对于vector而言是真的)。
for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++)
但是这会使代码更加脆弱,因为typedef中的简单更改OdpVec会在没有任何警告的情况下破坏代码。因此,为了使这与正常的迭代器使用更加一致,在检查end()时使用!=并且更喜欢预增量运算符:
for (OdpVec::iterator iter = vec.begin(); iter != vec.end(); ++iter)
答案 2 :(得分:2)
如果您只想影响那个单独的shared_ptr引用,只需分配给它。shared_ptr::reset
销毁shared_ptr
中已有的内容。
编辑:在回复评论时,您可以通过将for循环的主体更改为:
来修复它if ((*iter)->id == id)
{
shpoutOdp = *iter;
return true;
}
EDIT2:那都说了,你为什么不在这里使用std :: find_if?
#include <iostream>
#include <memory>
#include <vector>
#include <algorithm> //for std::find_if
#include <functional> //for std::bind
struct Odp
{
int id;
int GetId()
{
return id;
}
Odp(int id)
{
this->id = id;
}
~Odp()
{
std::cout << "Destructing Odp " << id << std::endl;
}
};
typedef std::vector<shared_ptr<Odp> > OdpVec;
int main()
{
OdpVec vec;
vec.push_back(std::shared_ptr<Odp>(new Odp(0)));
vec.push_back(std::shared_ptr<Odp>(new Odp(1)));
vec.push_back(std::shared_ptr<Odp>(new Odp(2)));
OdpVec::iterator foundOdp = std::find_if(vec.begin(), vec.end(),
std::bind(std::equal_to<int>(), 0, std::bind(&Odp::GetId,_1)));
bool found = foundOdp != vec.end();
}
答案 3 :(得分:1)
关于shared_ptr
的好处是它在内部处理引用计数。您无需手动递增或递减永远。 (这就是shared_ptr
不允许你这样做的原因)
当您致电reset
时,它只会将当前shared_ptr
设置为指向另一个对象(或null)。这意味着现在对reset
之前指向的对象的引用少了一个,所以从这个意义上说,ref计数器已经递减了。但这不是你应该调用递减参考计数器的功能。
你永远不需要这样做。只需让shared_ptr
超出范围,它就会减少引用计数。
这是RAII的一个例子。
您需要管理的资源(在这种情况下,shared_ptr
指向的对象)绑定到堆栈分配的对象(shared_ptr
本身),以便自动管理其生命周期。 shared_ptr
的析构函数可确保在适当的时候释放指向的对象。