我正在尝试理解以下内容(让我们假装MyStorageClass很大):
class MyStorageClass
{
public:
string x;
string y;
string z;
};
class storage
{
public:
storage();
~storage()
{
vector<MyStorageClass *>::iterator it = myVec.begin(); it != myVec.end(); it++)
{
delete *it;
}
vector<MyStorageClass*> getItems()
{
for(int i = 0; i < 10; ++i)
{
v.push_back(new MyStorageClass());
}
return v;
}
private:
vector<MyStorageClass*> v;
};
main()
{
storage s;
vector<MyStorageClass*> vm = s.getItems();
}
从我的描述中,当s
返回向量并分配给vm
时,这将作为副本(按值)完成。因此,如果s
超出范围并将其称为析构函数,vm
具有自己的副本,其结构不受影响。但是,通过价值传递效率不高。因此,如果您将其更改为通过引用传递:
vector<MyStorageClass*>& getItems()
{
for(int i = 0; i < 10; ++i)
{
v.push_back(new MyStorageClass());
}
return v;
}
您传递v
的内存位置(在类Storage中)。但您仍然使用=
运算符将其副本分配给Main类中的向量vm
。因此,vm
独立于v
,如果调用Storage
析构函数vm
不受影响。
最后,如果getItems
返回了引用,则在main中您有以下内容:
main()
{
storage s;
vector<MyStorageClass*> &vm = s.getItems();
}
现在,vm
保留v
的地址。因此它受Storage Destructor的影响。
问题:我上面所说的是真的吗?
答案 0 :(得分:7)
是的,您已正确理解这一点。现在,如果你想把它提升到一个新的水平,首先找出原因,这是坏事:
s
- 对象被销毁,您获得的引用将成为悬挂引用,从而打破引用始终有效的不变量。更好的解决方案是storage
公开从begin
返回相应迭代器的方法end
和s
。或者,您可以将Visitor - 模式用于需要在s
上运行的算法。
另外,在你的情况下,似乎向量s
应该拥有它包含的对象。这将是使用boost::ptr_vector
的一个很好的指标。
答案 1 :(得分:0)
我认为你所陈述的是真实的,但我对此目的感到有些困惑。
vector<MyStorageClass*> &vm = s.getItems();
通过引用获取向量。但是向量包含指针,因此超出范围的向量不会导致任何析构函数首先运行 - 析构函数只有在这些是某种类型的智能指针时才会运行(即使它依赖于它)。
因此,您可以愉快地通过值传递指针向量而不会出现问题。我相信你会通过引用来节省一些效率,但我认为它并不像你想象的那么严重。
此外,您的指向对象是使用new(动态)创建的,因此您可以以相同的方式按值返回向量,而不必担心丢失指向的对象。
所以,再次,我认为你的逻辑很好,你的参考方式更有效率,但我只是想确保你知道它可以双向无问题地工作:)(并且因为它的一个指针的矢量,按值也不算太差。)
PS:就参考而言,你必须担心悬挂可能比你在Bjorn上面指出的更令人沮丧。如果您曾经使用过string.c_str(),那么您可能已经习惯了这一点。如果从字符串中获取.c_str()且原始字符串超出范围,则.c_str()返回的指针悬空(指向不再用于此目的的内存)并访问它会导致未定义的行为。因此,by值可能是更好的选择(但它确实取决于你的设计 - 例如,如果在应用程序的持续时间内这将是一个单独的持久性,那么悬挂可能不是问题。)答案 2 :(得分:0)
即使向量复制了它的值,你的例子中的值也是指针,而不是指向的对象。所以“vm
拥有它自己的副本”并不完全正确:第一段代码中的结果向量将包含指针的副本,但不包含MyStorageClass
的副本他们指向的对象;所以,实际上在你的所有3个代码示例中,如果调用存储析构函数,vm
中存储的指针将不再有效!
但是,如果你需要确保Storage
在上次访问其中一个MyStorageClass对象之前没有被销毁,那么,在所提出的方法中,第三种方式将是首选方法,因为矢量数据(即指针)仅在存储器中存在一次。您应该考虑返回const
向量,但是,getItems的每个调用者都可以通过返回的引用修改Storage类的v
向量。
正如其他人已经指出的那样,暴露载体本身可能并不是一个好主意;您可以考虑使用迭代器或访问者模式。
此外,指针的使用 - 不是绝对需要 - 在较大的项目中通常相当不赞成。考虑使用智能指针,例如std::auto_ptr
(虽然由于奇怪的复制语义而不是很值得推荐),或者更容易理解boost::shared_ptr
/ std::tr1::shared_ptr
。
正如旁注,第一个和第二个代码示例(至少在大多数现代编译器中)具有完全相同的性能,因为在第一种情况下,编译器可以优化临时返回值(请查看“返回”价值优化“)。
答案 3 :(得分:0)
请注意,如果复制了类存储的对象,您的存储类将导致问题。由于您不提供复制构造函数或赋值运算符,因此将使用默认值。默认情况下会盲目地复制指针向量,现在你有两个存储对象,它们都试图删除向量中的指针。
答案 4 :(得分:0)
根据我的理解,当
s
返回向量并分配给vm
时,这将作为副本(按值)完成。因此,如果s
超出范围并将其称为析构函数,则vm
具有自己的副本,其结构不受影响。
普通指针(T*
)后面没有std::vector
的复制构造函数或赋值运算符(尽管你可以使用一个可复制的智能指针类)。由于指针指向(“指向”)的内容未被复制,因此复制操作是浅拷贝。虽然s.v
上的操作不会影响vm
(反之亦然),但影响从一个访问的指针的任何内容都会影响另一个,因为它们是相同的。
如果要在s.v
而不是MyStorageClass*
中存储经过适当实施的智能指针,标准std::vector
复制操作可能会生成深层副本,因此{{1}的内容可以改变而不会以任何方式影响s.v
的内容(反之亦然)。复制指针的复制操作将使用指向类的复制操作来复制指向的对象。因此,每个尖头对象只能由一个复制指针指向。
或者(正如其他人提到的那样),您可以使用允许共享所有权的智能指针,让它管理指向的对象,取消vm
中的delete
调用。
但是,在某些情况下,这是正确的,特别是当变异容器的一个实例不得影响另一个(你提到的情况)时,在这种情况下,如果没有副本,你就无法逃脱。正确胜过王牌。一般来说,您可以使用copy-swap idiom来减少由于临时创建而导致的低效率。据我所知,大多数STL实现不使用但是,通过值传递效率不高。
~storage
的复制交换。仅在更改向量时执行副本的Copy-on-write也可以提供帮助。线程化使copy-on-write变得复杂,并且会导致效率低下。
现在,
std::vector
保留vm
的地址。
v
没有地址。虽然引用可能使用引擎盖下的指针(它们也可能不会;它们甚至可能不需要额外的存储,根据C ++ 03的§8.3.2-3),在语言级别引用不是指针。考虑您可以使用空指针,但不能使用空引用。更准确的说法是vm
别名为vm
(v
且vm
引用相同的对象),这就是v
上的操作会影响的原因v
(反之亦然)。