我有一个使用boost::shared_ptr
的程序,特别是依赖于use_count
执行优化的准确性。
例如,假设一个带有两个参数指针的加法运算,称为lhs和rhs。假设他们都有类型shared_ptr<Node>
。当需要执行添加时,我将检查use_count
,如果我发现其中一个参数的引用计数恰好为1,那么我将重用它来执行操作。如果两个参数都不能重用,我必须分配一个新的数据缓冲区并执行不合适的操作。我正在处理庞大的数据结构,因此就地优化非常有用。
因此,我无法无理由地复制shared_ptr
,即每个函数都通过引用或const引用获取shared_ptr
s,以避免扭曲use_count
。
我的问题是:我有时会将shared_ptr<T> &
转换为shared_ptr<T const> &
,但如何在不扭曲使用次数的情况下执行此操作? static_pointer_cast
返回一个新对象而不是引用。我倾向于认为只需投射整个shared_ptr
就可以了,如:
void f(shared_ptr<T> & x)
{
shared_ptr<T const> & x_ = *reinterpret_cast<shared_ptr<T const> *>(&x);
}
我非常怀疑这符合标准,但正如我所说,它可能会奏效。有没有办法保证安全和正确?
更新以集中问题
批评设计并没有帮助回答这篇文章。有两个有趣的问题需要考虑:
(boost::shared_ptr
的作者或std::tr1::shared_ptr
的标准)shared_ptr<T>
和shared_ptr<T const>
是否有相同的保证?布局和行为?
如果(1)为真,那么上面是reinterpret_cast的合法使用吗?我认为你很难找到一个为上面的例子生成失败代码的编译器,但这并不意味着它是合法的。无论您的答案是什么,您能否在C ++标准中找到对它的支持?
答案 0 :(得分:10)
我有时会将
shared_ptr<T> &
转换为shared_ptr<T const> &
,但如何在不扭曲使用次数的情况下执行此操作?
你没有。这个概念是错误的。考虑使用裸指针T*
和const T*
会发生什么。当您将T*
投射到const T*
时,您现在有两个指针。您没有对同一指针的两个引用;你有两个指点。
为什么智能指针会有所不同?你有两个指针:一个到T
,一个到const T
。他们都共享同一个对象的所有权,因此您正在使用两个。因此,use_count
应该是2,而不是1。
您的问题是您试图超越use_count
的含义,为其他目的选择其功能。简而言之:你做错了。
您对shared_ptr
use_count
的{{1}}所做的描述是...... 可怕的。你基本上是说某些函数选择其中一个参数,调用者明确地使用它(因为调用者显然仍在使用它)。并且调用者不知道声明了哪一个(如果有的话),因此调用者不知道在函数之后参数的状态是什么。修改这样的操作的参数通常不是一个好主意。
另外,你所做的只有在你通过引用传递shared_ptr<T>
时才能工作,这本身并不是一个好主意(比如常规指针,智能指针几乎总是应该按值)。
简而言之,你正在使用一个非常常用的具有明确定义的习语和语义的对象,然后要求它以一种几乎永远不会使用的方式使用,具有专门的语义,与每个人实际使用它们的方式背道而驰。这不是一件好事。
你已经有效地创建了co-optable指针的概念,一个可以处于3种使用状态的共享指针:空,仅供给你的人使用,因此你可以从中窃取,并在使用中不止一个人,所以你不能拥有它。这不是shared_ptr
支持的语义。所以你应该编写自己的智能指针,以更自然的方式提供这些语义。
识别你所拥有的指针实例数与你拥有的实际用户数之间差异的东西。这样,您可以正确地传递它的值,但是你有一些方法可以说你正在使用它并且不希望其中一个其他函数声明它。它可以在内部使用shared_ptr
,但它应该提供自己的语义。
答案 1 :(得分:4)
static_pointer_cast
是适合这项工作的工具 - 您已经确定了这一点。
它的问题不是它返回一个新对象,而是它保持旧对象不变。你想摆脱非常量指针,继续使用const指针。你真正想要的是static_pointer_cast< T const >( std::move( old_ptr ) )
。但是rvalue引用没有重载。
解决方法很简单:手动使旧指针无效,就像std::move
那样。
auto my_const_pointer = static_pointer_cast< T const >( modifiable_pointer );
modifiable_pointer = nullptr;
它可能比reinterpret_cast
略慢,但它更有可能工作。不要低估库实现的复杂程度,以及它在滥用时如何失败。
旁白:使用pointer.unique()
代替use_count() == 1
。某些实现可能使用没有缓存使用计数的链表,使use_count()
O(N),而unique
测试仍为O(1)。标准建议unique
进行写入优化复制。
编辑:现在我看到你提到了
我无法无理由地复制
shared_ptr
,即每个函数都通过引用或const引用获取shared_ptr
以避免扭曲use_count
。
这是做错了。您已在shared_ptr
已经执行的操作上添加了另一层所有权语义。它们应该按值传递,std::move
用于呼叫者不再需要所有权的地方。 如果分析器说你花时间调整引用计数,那么你可以在内部循环中添加一些引用指针。但作为一般规则,如果您不能设置指向nullptr
的指针,因为您不再使用它,但其他人可能会使用它,那么您实际上已经失去了对所有权的追踪。
答案 2 :(得分:1)
如果将shared_ptr
强制转换为其他类型,而不更改引用计数,则表示您现在有两个指向同一数据的指针。因此,除非您删除旧指针,否则无法在不“扭曲引用计数”的情况下使用shared_ptr
执行此操作。
我建议你在这里使用原始指针,而不是不想使用shared_ptr
s的功能。如果您有时需要创建新引用,请使用enable_shared_from_this
为现有原始指针派生新shared_ptr
。
答案 3 :(得分:0)
当需要执行添加时,我将检查
use_count
,如果我发现其中一个参数的引用计数恰好为1,那么我将重用它来执行操作到位。
除非您在整个程序中应用其他规则来实现这一目标,否则这不一定有效。考虑:
shared_ptr<Node> add(shared_ptr<Node> const &lhs,shared_ptr<Node> const &rhs) {
if(lhs.use_count()==1) {
// do whatever, reusing lhs
return lhs;
}
if(rhs.use_count()==1) {
// do whatever, reusing rhs
return rhs;
}
shared_ptr<Node> new_node = ... // do whatever without reusing lhs or rhs
return new_node;
}
void foo() {
shared_ptr<Node> a,b;
shared_ptr<Node> c = add(a,b);
// error, we still have a and b, and expect that they're unchanged! they could have been modified!
}
相反,如果您按值获取智能指针:
shared_ptr<Node> add(shared_ptr<Node> lhs,shared_ptr<Node> rhs) {
而use_count()== 1则您知道您的副本是唯一的副本,重复使用它应该是安全的。
但是,将此作为优化使用时会出现问题,因为复制shared_ptr需要同步。很可能在整个地方进行所有这些同步所花费的成本远远超过重用现有的shared_ptrs所节省的成本。所有这些同步是因为建议不接受shared_ptr所有权的代码应该通过引用而不是值来获取shared_ptr。