我刚刚注意到您添加到问题中的代码。SharedPointers 不这样做。
SharedPointers 在“引用计数”方面是“共享的”,而不是在“数据共享”方面是“共享的”。用您的话来说,“数据共享”是由 .. 指针完成的。无论你想“分享”什么,指针指向它。然后,如果您更改它,每个人都会看到更新。但是你必须改变它,而不是指向它的指针。
也就是说,在这里,您要更新指针,并且希望每个人都看到正在更新的指针。因此,您必须逐个指针地持有指针。
也就是说,不要保留 ,而是vector<shared_ptr<Base>>
保留vector<shared_pointer<Base>*>
。
现在,当您想用新实例“全局替换”对象时,您可以shptr<Base>
用另一个新的 shptr 替换指针所持有的。
如果你不喜欢原始指针,你甚至可以使用vector<shared_pointer<sahred_pointer<Base>>
和 .reset 内部,同时保持外部不变。每个获得外部副本的人都会看到内部副本的更新。
// obj_array and container are a vector<shared_ptr<Base>*>
// or a vector<shared_ptr<shared_ptr<Base>>>
obj_array.push_back(new std::shared_ptr<Base>(new Base()); // note the 'new'
container.push_back(obj_array[0]]);
(*obj_array[0]) .reset(Child_A()); // note the '*'
obj_array[0] -> reset(Child_A()); // or, just in short
编辑#2:
在您发表评论“还有一点,obj_array 不必是指向指针的向量。它可以只是一个vector<shared_ptr<Base>>
。如果我错了,请纠正我”:
这取决于你想达到什么目标。您可以随心所欲地将事物保存在向量中 - 它只会影响其使用的某些场景。现在让我描述一个非常抽象的设置。你有一个主容器,它以某种方式容纳一些东西。您的应用程序中称为 A、B、C 的部分会定期获取这些内容并对其执行某些操作。B 部分和 C 部分有时会出于某种目的而在内部记住事物。现在假设:
- 案例 1:vector 持有
Base*
对象,
- 案例 2:vector 持有
shared_ptr<Base>
对象,
- 案例 3:vector 持有 Base**
- 案例4:向量成立
shptr<shptr<Base>>
当然还有更多可能的情况,但让我们修剪一下。现在,您的应用程序正在运行并且主容器中已经有一些对象。模块 A、B、C 已经处理了一些东西,可能模块 B 和 C 已经记住了一些对象。现在应用程序需要将主容器中的第 5 个项目替换为new Bar()
.
情况1:
向量是Base*
。Bar 当然实现了 Base,所以 new Bar() 可以直接赋值给vector[4]
. 当然,您已经决定如何处理旧元素。删除还是忘记?然后,vector[4]=new Bar()
执行,从现在开始每个读取这个主向量的人都会在第 5 个位置看到新对象。
但是,问题还没有结束:模块 B 和 C 可能仍然知道旧对象。由于向量的元素是 Base*(原始指针),那些 B/C 已经复制了指向旧对象的指针的原始值,所以现在唯一的解决方案是明确告诉 B 和 C 也执行替换。
因此,案例 1 以类似于以下代码的形式结束。它分为三个阶段:初始设置示例、运行时操作示例和最终清理示例。
vector<Base*> vector;
vector.resize( 10 );
///// .... later ....
Base* olditem = vector[ 4 ];
Base* newitem = new Bar();
bool iWillDeleteTheOld = well_somehow_decide();
vector[4] = newitem;
moduleB->updateAfterReplace(olditem, newitem, iWillDeleteTheOld);
modulec->updateAfterReplace(olditem, newitem, iWillDeleteTheOld);
if(iWillDeleteTheOld)
delete olditem;
///// .... later ....
for( ... idx ...)
delete vector[idx];
vector.resize(0);
modulesB/C 读取向量项Base*
,如果缓存它 - 他们将其缓存为Base*
.
请注意,这段代码会导致您updateAfterReplace
在每个“模块”中编写附加函数,这些“模块”可能仍会记住旧对象。而且您需要协调它们的内部updateAfterReplace
,以免在您要在这里执行的情况下尝试删除旧对象,否则您将在这里双重删除它并严重崩溃。我在这里通过告诉他们是否iWillDeleteTheOld
. 如果他们知道我会这样做,他们将跳过旧的对象删除阶段。但是,如果我决定不删除它(iwilldelete=false),他们仍然可能决定自行删除它。
但是,这只是一个适当的所有权管理问题,我们不在这里讨论它。
案例二:
向量是shared_ptr<Base>
。Bar 当然实现了 Base,所以 new Bar() 可以直接分配给vector[4]
(没有变化)。当然,您已经决定如何处理旧元素(没有变化)。
(更改)但是,由于您将指针保留为shared_ptr
,因此所有权没有问题:您只需覆盖/释放指针,其余的将由 shptr 处理。如果有人使用该对象,它将删除它。如果它仍然使用,它会保留它。
然后,vector[4]=new Bar()
执行,从现在开始每个读取这个主向量的人都会在第 5 个位置看到新对象。(没变)
但是,问题还没有结束:模块 B 和 C 可能仍然知道旧对象。由于向量的元素是sharedptr<Base>
,那些 B/C 已经复制了 shared_ptr-to-old-object,所以现在唯一的解决方案是明确告诉 B 和 C 也执行替换。(没有变化)
因此,案例 2 以
vector<shared_ptr<Base>> vector;
vector.resize( 10 );
///// .... later ....
sharedptr<Base> olditem = vector[4];
sharedptr<Base> newitem = new Bar();
vector[4].reset( newitem ); // <- THE LINE
moduleB->updateAfterReplace(olditem, newitem);
modulec->updateAfterReplace(olditem, newitem);
///// .... later ....
vector.resize(0);
modulesB/C 读取向量项sharedptr<Base>
,如果缓存它 - 他们将其缓存为sharedptr<Base>
. 任何减少Base*
都会将 case 转换为 Case1。
请注意如何“决定删除”和“对象删除”以及“我告诉你那个我删除它is gone. This is the benefit of
sharedptr”。但是,我仍然需要手动更新可能仍保存旧对象的所有其他缓存。
这是因为 THE LINE 不仅覆盖了 shared_ptr,而且还执行“可能删除”阶段:将 shared_ptr 从内部引用计数机制中分离出来,如果计数下降到零 - 删除对象。问题就在这里:它分离了。从vector[4] 中的 shptr复制的所有其他sharedptr
内容现在正在形成自己的引用计数组,并且它们仍然记得旧对象。他们没有更新内容。他们只是集体从 refcount=3 下降到 refcount=2。
案例3:
向量是Base**
。Bar 当然实现了 Base,因此 new Bar() 不能直接分配给vector[4]
: vector 现在拥有一个指向指针的指针,因此还需要额外的取消引用(change)。当然,您已经决定如何处理旧元素(没有变化)。删除还是忘记?(没变)
然后,*vector[4]=new Bar()
执行,从现在开始每个读取这个主向量的人都会在第 5 个位置看到新对象。(没变)
这就是问题的结束。(改变)
因此,案例 3 以
vector<Base**> vector;
for(int i = 0; i<10; ++i)
vector.push( new Base* );
///// .... later ....
Base* olditem = * vector[4]; // note the dereference
Base* newitem = new Bar();
bool iWillDeleteTheOld = well_somehow_decide();
* vector[4] = newitem; // note the dereference
if(iWillDeleteTheOld)
delete olditem;
///// .... later ....
for( ... idx ...)
{
delete * vector[idx]; // delete the object
delete vector[idx]; // delete the pointer
}
vector.resize(0);
modulesB/C 读取向量项Base**
,如果缓存它 - 他们将其缓存为Base**
. 任何减少Base*
都会将 case 转换为 Case1。
首先,请注意,现在您的向量必须用指针完全初始化。不需要那样做,你可以即时做,但在这两行“注意取消引用”,你必须绝对确定在 vector[nth] 有一个正确分配的pointer-to-Base*
(所以,Base** ,所以new Base*
)。
由于vector的元素现在是Base**
,那些B/C模块可能已经复制了Base**
-not Base*
!因此,用自然语言来说,模块 B/C 现在记住指针所在的位置,而不是对象所在的位置。如果您将指针更改为指向其他地方,他们会立即看到它,因为他们首先查看指针所在的位置,然后他们会在那里找到..新版本的指针。
这样,级联更新消失了,对象所有权/删除也简化了,但仍然存在。但!此外,出现了新的片段:由于您必须分配额外的指针,因此您还必须在某些时候删除它们。
案例4:
vector<sharedptr<sharedptr<Base>>> vector;
for(int i = 0; i<10; ++i)
vector.push( new sharedptr<Base> );
///// .... later ....
(* vector[4] ).reset( new Bar() ); // note the dereference
///// .... later ....
vector.resize(0);
modulesB/C 读取向量项sharedptr<sharedptr<Base>>
,如果缓存它 - 他们将其缓存为sharedptr<sharedptr<Base>>
. 任何减少Base*
或sharedptr<Base>
将案例分别转换为案例 1 或案例 2。
我希望你现在已经了解了所有的差异,所以我不会重复这里发生的细节。
免责声明:所有示例都只是说明性的。这些代码都没有经过测试,这些代码都不是“完整的”。例如,缺少很多错误检查。它们只是为了展示机制的骨架。由于拼写错误等,它们甚至可能无法编译。
最后一句话#1:当然,您可以自由混合指针和共享指针。代替shared_ptr<shared_ptr<Base>>
,您可以使用shared_ptr<Base*>
orshared_ptr<Base>*
或Base**
如已经看到的那样。它只影响初始化和清理。不是替换更新问题。
最后一句话#2:请注意,还有参考资料。如果模块 B/C通过引用捕获了Base*
or ,那么引入or就没有意义了。引用捕获已经引入了所需的相同的多一级间接。事实上,引用 & 在内部只是一个原始指针,事实上也是如此。shared_ptr<Base>
Base**
shared_ptr<shared_ptr<Base>>.
&-to-Base*
Base**
最后一句话#3:
我已经说过了,但让我再说一遍。核心问题是shared_ptr
引用计数的“共享”,而不是数据。因此:
shared_ptr<Foo> first = new Foo(1);
shared_ptr<Foo> second = first;
shared_ptr<Foo> third = second;
// now first == Foo#1 \
// now second == Foo#1 | refcount = 3
// now third == Foo#1 /
third.reset( new Bar(2) );
// now first == Foo#1 \ refcount = 2
// now second == Foo#1 /
// now third == Bar#2 - refcount = 1
second.reset( new Asdf(3) );
// now first == Foo#1 - refcount = 1
// now second == Asdf#3 - refcount = 1
// now third == Bar#2 - refcount = 1
first.reset( third );
// now first == Bar#2 \
// now second == Asdf#3 - refcount = 1 | - refcount = 2
// now third == Bar#2 /
// and Foo#1 gets deleted