将相同的指针发送到两个不同的shared_ptr
是不好的,它会导致双重释放,如下所示:
int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD
您可以使用std::enable_shared_from_this
完成相同的目的:
class Good: public std::enable_shared_from_this<Good>
{
public:
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
};
int main()
{
std::shared_ptr<Good> gp1(new Good);
std::shared_ptr<Good> gp2 = gp1->getptr();
}
但这仍然无法防范:
class Good: public std::enable_shared_from_this<Good>
{
public:
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
};
int main()
{
Good* p = new Good;
std::shared_ptr<Good> gp3(p);
std::shared_ptr<Good> gp4(p); // BAD
}
如果你有这样的代码可能会成为一个问题:
void Function(std::shared_ptr<Good> p)
{
std::cout << p.use_count() << '\n';
}
int main()
{
Good* p = new Good;
std::shared_ptr<Good> p1(p);
Function(p); // BAD
}
为什么我会在有智能指针时使用常规指针?因为在性能关键代码(或为了方便)中,shared_ptr或weak_ptr的开销是不合需要的。
为防止出现这种错误,我已经完成了:
class CResource : public shared_ptr<Good>
{
public:
CResource()
{
}
CResource(std::shared_ptr<CBaseControl> c)
: CResource(c)
{
}
private:
CResource(CBaseControl* p)
{
}
};
void Function(CResource p)
{
std::cout << p.use_count() << '\n';
}
int main()
{
Good* p = new Good;
CResource p1(std::shared_ptr<Good>(p));
Function(p); // Error
}
如果有人试图用指针而不是Function
来调用shared_ptr
,这会导致编译器出错。但这并不妨碍某人声明void Function(std::shared_ptr p)
,但我认为这不太可能。
这还不错吗?有没有更好的方法呢?
答案 0 :(得分:26)
解决方案很简单:首先没有原始指针拥有内存。这种模式:
int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD
根本不应该存在。从你的代码库中消除它。 {+ 1}}在C ++ 11中唯一合法的地方是作为参数到构造函数调用智能指针(或非常低级别的东西)
即。有这样的代码:
new
或者更好(不再涉及裸std::shared_ptr<int> p1(new int);
):
new
请注意,在代码中使用原始指针是很好的(但我在大多数C ++代码中都会问到这一点)。但是如果你使用原始指针,不要让它们拥有内存。要么指向自动存储(不需要资源管理),要么使用auto p1 = std::make_shared<int>();
并通过其unique_ptr
成员函数访问原始指针。
答案 1 :(得分:10)
这个例子甚至没有编译:
void Function(std::shared_ptr<Good> p)
{
std::cout << p.use_count() << '\n';
}
int main()
{
Good* p = new Good;
std::shared_ptr<Good> p1(p);
Function(p); // BAD
}
shared_ptr
有一个explicit
构造函数正好来阻止这种情况发生。
要进行编译,您需要编写:
Function( std::shared_ptr<Good>(p) );
这显然是错误的,如果有人会犯这样的错误,他们就有可能这样做:
Function( CResource(std::shared_ptr<Good>(p)) );
那你为什么要写CResource
呢?它添加了什么?
扩展Konrad Rudolph的优秀答案:
关于如何避免问题的问题的答案是遵循RAII惯用法,但完全按照 。
我们将忽略甚至不编译的示例并查看其上方的示例:
Good* p = new Good;
std::shared_ptr<Good> gp3(p);
std::shared_ptr<Good> gp4(p); // BAD
该代码未能遵循RAII惯用法。您获得了资源:
Good* p = new Good;
但是不要初始化RAII类型。的 BAD 强>
然后使用一些现有资源初始化对象:
std::shared_ptr<Good> gp3(p);
这也是 BAD 。您应该在获取资源的同时初始化RAII类型,而不是单独初始化(甚至不能仅用一行分隔。)
然后你重复同样的错误:
std::shared_ptr<Good> gp4(p); // BAD
您已将此行标记为“BAD”,但实际上前两行只是一样糟糕。第三行将导致未定义的行为,但前两行允许该错误进入,当它应该变得更加困难时。如果你从来没有Good*
闲置,那么你就不能用它来初始化gp4
,你需要说shared_ptr<Good> gp4(gp3.get())
这显然是 错误没有人会这样做。
规则很简单:不要使用原始指针并将其放在shared_ptr
中。您未分配的原始指针不是资源获取,因此请勿将其用于初始化。这同样适用于Function
内部,它不应该使用原始指针并使用它来初始化获得该类型所有权的shared_ptr
。
这是C ++,所以不可能防止所有错误的编写代码的方法,你不能阻止有充分动机的白痴自己在脚下射击,但所有的指导方针和工具都可以防止它,如果你遵循指导方针。
当您有一个强制的接口通过传递原始指针来转移所有权时,请尝试替换接口,以便使用unique_ptr
进行所有权转移,或者如果它完全不可能要做到这一点,然后尝试使用unique_ptr
将接口包装在一个更安全的版本中进行所有权转移,并且只作为最后的手段,使用危险的界面,但记录非常明确地所有权转移