标题说。
糟糕的样本:
std::vector<Point>* FindPoints()
{
std::vector<Point>* result = new std::vector<Point>();
//...
return result;
}
如果我稍后删除vector
会有什么问题?
我主要用C#编程,所以在C ++上下文中这个问题对我来说并不是很清楚。
答案 0 :(得分:11)
根据经验,您不这样做,因为您在堆上分配的越少,泄漏内存的风险就越小。 :)
std::vector
也很有用,因为它以RAII方式自动管理用于向量的内存;现在,通过在堆上分配它,您需要显式释放(使用delete result
)以避免泄漏其内存。由于例外,事情变得复杂,这可能会改变您的返回路径并跳过您放置的任何delete
。 (在C#中,您没有这样的问题,因为垃圾收集器会定期调用无法访问的内存)
如果要返回STL容器,您有几种选择:
只需按值返回;从理论上讲,由于在返回result
的过程中产生的临时性,你应该产生一个复制品,但是较新的编译器应该能够使用NRVO 1来删除副本。 SUP>。可能还有std::vector
个实现,像许多std::string
实现那样实现了写时复制优化,但我从未听说过。
在C ++ 0x编译器上,移动语义应该触发,避免任何副本。
std::auto_ptr
(或C ++ 0x中的std::unique_ptr
),并将函数的返回类型更改为std::auto_ptr<std::vector<Point > >
;这样,你的指针总是封装在一个堆栈对象中,当函数退出时(以任何方式)会自动销毁,如果它仍由它拥有,则会销毁vector
。此外,完全清楚谁拥有返回的对象。result
向量成为调用者通过引用传递的参数,并填充该向量而不是返回新向量。std::copy
+ std::back_inserter
或其他任何内容将这些数据存储在所需的容器中。看不多(代码正确可能很棘手),但值得一提。答案 1 :(得分:6)
动态创建任何东西都是不好的做法,除非真的有必要。很少有理由动态创建容器,因此通常不是一个好主意。
编辑:通常,不要担心返回容器的速度有多快或多慢,而是大多数代码只应处理容器中的迭代器(或两个)。
答案 2 :(得分:2)
一般情况下动态创建对象在C ++中被认为是一种不好的做法。如果您的“// ...”代码抛出异常怎么办?你将永远无法删除该对象。简单地做起来更简单,更安全:
std::vector<Point> FindPoints()
{
std::vector<Point> result;
//...
return result;
}
更短,更安全,更直接......至于性能,现代编译器会在返回时优化掉副本,如果它们不能,移动构造函数将被执行,因此这仍然是一个廉价的操作。
答案 3 :(得分:0)
也许您指的是最近的这个问题:C++: vector<string> *args = new vector<string>(); causes SIGABRT
一个班轮:这是不好的做法,因为它是一种容易出现内存泄漏的模式。
您强制调用者接受动态分配并负责其生命周期。返回指针返回的是静态缓冲区,某个其他API(或对象)拥有的缓冲区,或者调用者现在拥有的缓冲区,这是不明确的。你应该避免使用任何语言(包括普通C语言)的这种模式,除非从函数名中清楚地知道发生了什么(例如strdup,malloc)。
通常的方法是这样做:
void FindPoints(std::vector<Point>* ret) {
std::vector<Point> result;
//...
ret->swap(result);
}
void caller() {
//...
std::vector<Point> foo;
FindPoints(&foo);
// foo deletes itself
}
所有对象都在堆栈中,所有删除都由编译器处理。或者只是按值返回,如果你正在运行C ++ 0x编译器+ STL,或者不介意副本。
答案 4 :(得分:0)
我喜欢Jerry Coffin的回答。此外,如果要避免返回副本,请考虑将结果容器作为引用传递,有时可能需要使用swap()方法。
void FindPoints(std::vector<Point> &points)
{
std::vector<Point> result;
//...
result.swap(points);
}
答案 5 :(得分:0)
编程是找到妥协的艺术。动态分配的内存当然可以占有一席之地,我甚至可以考虑使用std::vector<std::vector<T>*>
获得代码复杂性和效率之间良好折衷的问题。
但是std::vector
可以很好地隐藏动态分配数组的大多数需求,而托管指针很多时候只是动态分配单个实例的完美解决方案。这意味着发现非托管动态分配容器(或实际上动态分配任何内容)是C ++中最佳折衷方案的情况并不常见。
在我看来,这不会使动态分配变得“糟糕”,但如果你在代码中看到它就会“怀疑”,因为很有可能找到更好的解决方案。
例如,在您的情况下,我认为没有理由使用动态分配;只是使函数返回std :: vector将是高效和安全的。在分配给新声明的向量时,将使用任何体面的编译器Return Value Optimization,如果需要将结果分配给现有向量,您仍然可以执行以下操作:
FindPoints().swap(myvector);
不会对数据进行任何复制,只会对某些指针进行复杂处理(请注意,由于C ++规则有时令人讨厌禁止将临时值作为非const引用传递,因此您无法使用明显更自然的myvector.swap(FindPoints())
)。
根据我的经验,动态分配对象的最大需求来源是复杂的数据结构,其中可以使用多个访问路径到达相同的实例(例如,实例同时在双向链表中并由地图索引) 。在标准库中,容器始终是包含对象的唯一所有者(C ++是一种复制语义语言),因此如果没有指针和动态分配概念,可能很难有效地实现这些解决方案。
通常情况下,您可以使用标准容器进行合理足够的妥协(可能需要支付一些您可以避免的额外O(log N)查找)并且考虑到更简单的代码,可以将IMO作为最佳折衷方案在大多数情况下。