Here's a notable video (Stop teaching C) about that paradigm change to take in teaching the c++ language.
这里有一个值得注意的视频(停止教C语言),关于在教c++语言时的范式变化。
And an also notable blog post
还有一个值得注意的博客
我有一个梦想……
I'm dreaming of so called C++ courses/classes/curriculae will stop teaching (requiring) their students to use: ...
我梦想着所谓的c++课程/课程/课程将停止教授(要求)他们的学生使用:……
Since C++11 as established standard we have the Dynamic memory management facilities aka smart pointers.
Even from earlier standards we have the c++ standard Containers library as a good replacement for raw arrays (allocated with new T[]
) (notably usage of std::string
instead of c-style NUL
terminated character arrays).
自从c++ 11作为公认的标准,我们有动态内存管理设施,也就是智能指针。甚至从早期的标准开始,我们就有c++标准容器库作为原始数组(使用新的T[]分配)的良好替代(特别是使用std::string而不是c风格的NUL终止字符数组)。
Question(s) in bold:
问题以粗体(s):
Let aside the placement new
override, is there any valid use case that can't be achieved using smart pointers or standard containers but only using new
and delete
directly (besides implementation of such container/smart pointer classes of course)?
放置新覆盖时,是否存在使用智能指针或标准容器不能实现的有效用例,但只使用新的和直接删除(当然,除了实现这种容器/智能指针类)?
It's sometimes rumored (like here or here) that using new
and delete
handrolled can be "more efficient" for certain cases. Which are these actually? Don't these edge cases need to keep track of the allocations the same way as standard containers or smart pointers need to do?
有时传闻(比如这里或这里),在某些情况下使用新的和删除手卷可以“更有效”。而这些是真的吗?难道这些边界情况不需要像标准容器或智能指针那样跟踪分配吗?
Almost the same for raw c-style fixed size arrays: There is std::array
nowadays, which allows all kinds of assignment, copying, referencing, etc. easily and syntactically consistent as expected by everyone. Are there any use cases to choose a T myArray[N];
c-style array in preference of std::array<T,N> myArray;
?
对于原始c风格的固定大小的数组,几乎是一样的:现在有std::array,它允许所有类型的赋值、复制、引用等都很容易,并且与每个人的期望一致。是否有任何用例选择T myArray[N];:数组
Regarding interaction with 3rd party libraries:
关于与第三方图书馆的互动:
Assumed a 3rd party library returns raw pointers allocated with new
like
假设第三方库返回用新的like分配的原始指针
MyType* LibApi::CreateNewType() {
return new MyType(someParams);
}
you can always wrap that to a smart pointer to ensure that delete
is called:
你可以将它封装到一个智能指针上,以确保删除被调用:
std::unique_ptr<MyType> foo = LibApi::CreateNewType();
even if the API requires you to call their legacy function to free the resource like
即使API要求您调用它们的遗留函数来释放资源,比如
void LibApi::FreeMyType(MyType* foo);
you still can provide a deleter function:
您仍然可以提供一个删除函数:
std::unique_ptr<MyType, LibApi::FreeMyType> foo = LibApi::CreateNewType();
I'm especially interested in valid "every day" use cases in contrast to academic/educational purpose requirements and restrictions, which aren't covered by the mentioned standard facilities.
That new
and delete
may be used in memory management / garbage collector frameworks or standard container implementation is out of question1.
与学术/教育目的需求和限制相比,我对有效的“每天”用例特别感兴趣,这些需求和限制不包括在上述标准设施中。在内存管理/垃圾收集器框架或标准容器实现中可能使用的新和删除是不可能的。
One major motivation ...
... to ask this question is to give an alternative approach vs any (homework) questions, which are restricted to use any of the constructs mentioned in the title, but serious questions about production ready code.
…问这个问题是给一个替代的方法和任何(家庭作业)问题,这些问题被限制使用标题中提到的任何结构,但是关于生产准备代码的严肃的问题。
These are often referred to as the basics of memory management, which is IMO blatantly wrong/misunderstood as suitable for beginners lectures and tasks.
这些通常被称为记忆管理的基础,这在我看来显然是错误的/被误解的,适合初学者的演讲和任务。
1)Add.: Regarding that paragraph, this should be a clear indicator that new
and delete
isn't for beginner c++ students, but should be left for the more advanced courses.
1)添加。:关于那一段,这应该是一个明确的指示,新的和删除不适合初学c++的学生,而应该留给更高级的课程。
19 个解决方案
#1
24
When ownership should not be local.
当所有权不应该是本地的时候。
As an example, a pointer container may not want ownership over the pointers in it to reside in the pointers themselves. If you try to write a linked list with forward unique ptrs, at destruction time you can easily blow the stack.
作为一个例子,指针容器可能不希望对它的指针拥有所有权,因为它驻留在指针本身中。如果您试图用唯一的ptrs写一个链表,在销毁时,您可以很容易地销毁堆栈。
A vector
-like container of owning pointers may be better suited to storing delete operation at the container or subcontainer level, and not at the element level.
拥有指针的矢量容器可能更适合在容器或子容器级别存储删除操作,而不是在元素级别。
In those and similar cases, you wrap ownership like a smart pointer does, but you do it at a higher level. Many data structures (graphs, etc) may have similar issues, where ownership properly resides at a higher point than where the pointers are, and they may not map directly to an existing container concept.
在这些和类似的情况下,您可以像智能指针那样封装所有权,但您是在更高的级别上进行的。许多数据结构(图等)可能有类似的问题,所有权正确地驻留在比指针所在的位置更高的位置,并且它们可能不会直接映射到现有容器概念。
In some cases it may be easy to factor out the container-ownership from the rest of the data structure. In others it may not.
在某些情况下,很容易将容器所有权从数据结构的其他部分中分离出来。在另一些国家,情况可能并非如此。
Sometimes you have insanely complex non-local non-reference counted lifetimes. There is no sane spot to put the ownership pointer in those cases.
有时你会有非常复杂的非本地非引用计数寿命。在这些情况下,没有理由把所有权指针放在那里。
Determining correctness here is hard, but not impossible. Programs that are correct and have such complex ownership semantics exist.
在这里确定正确性很困难,但并非不可能。存在正确且具有如此复杂所有权语义的程序。
All of these are corner cases, and few programmers should run into them more than a handful of times in a career.
所有这些都是一边倒的情况,很少有程序员在职业生涯中遇到过这样的情况。
#2
14
I'm going to be contrarian, and go on record as saying "no" (at least to the question I'm pretty sure you really intended to ask, for most of the cases that have been cited).
我将持相反意见,并公开地说“不”(至少我很确定你真的想问这个问题,对于大多数已经被引用的案例来说)。
What seem like obvious use-cases for using new
and delete
(e.g., raw memory for a GC heap, storage for a container) really aren't. For these cases, you want "raw" storage, not an object (or array of objects, which is what new
and new[]
provide respectively).
使用new和delete(例如,GC堆的原始内存,容器的存储)的明显用例实际上不是这样的。对于这些情况,您需要的是“原始”存储,而不是对象(或对象数组,它们分别是new和new[]提供的)。
Since you want raw storage, you really need/want to use operator new
and operator delete
to manage the raw storage itself. You then use placement new
to create objects in that raw storage, and directly invoke the destructor to destroy the objects. Depending on the situation, you might want to use a level of indirection to that though--for example, the containers in the standard library use an Allocator class to handle these tasks. This is passed as a template parameter, which provides a customization point (e.g., a way to optimize allocation based on a particular container's typical usage pattern).
由于您需要原始存储,所以您确实需要/希望使用操作符new和操作符delete来管理原始存储本身。然后使用placement new在原始存储中创建对象,并直接调用析构函数来销毁对象。根据具体情况,您可能需要使用一种间接的方式—例如,标准库中的容器使用分配器类来处理这些任务。它作为模板参数传递,提供一个定制点(例如,根据特定容器的典型使用模式优化分配的方法)。
So, for these situations, you end up using the new
keyword (in both the placement new and the invocation of operator new
), but not something like T *t = new T[N];
, which is what I'm pretty sure you intended to ask about.
因此,在这些情况下,您最终会使用new关键字(在放置new和调用operator new中),但不会使用T * T = new T[N];
#3
11
One valid use case is having to interact with legacy code. Especially if passing raw pointers to functions that take ownership of them.
一个有效的用例是必须与遗留代码交互。特别是如果将原始指针传递给拥有它们的函数。
Not all libraries you use may be using smart pointers and to use them you may need to provide or accept raw pointers and manage their lifetimes manually. This may even be the case within your own codebase if it has a long history.
并不是您使用的所有库都使用智能指针,要使用它们,您可能需要提供或接受原始指针并手动管理它们的生存期。如果您自己的代码库有很长的历史,这甚至可能是这样。
Another use case is having to interact with C which does not have smart pointers.
另一个用例是必须与没有智能指针的C交互。
#4
4
The OP specificly asks about how/when handrolling will be more efficient in an everyday use case - and I will address that.
OP专门询问如何/何时在日常用例中更有效地进行移植——我将对此进行说明。
Assuming a modern day compiler/stl/platform, there is not an every day use where handrolled use of new and delete will be more efficient. For the shared_ptr case i believe it will be marginal. In an extremely tight loop(s) there could be something to gain by just using raw new to avoid the ref counting (and find some other method of cleaning up - unless somehow imposed on you, you choose to use shared_ptr for a reason), but that is not an everyday or common example. For the unique_ptr there is not actually any difference, so i think it is safe to say that it is more of rumour and folklore and that performance wise it will not actually matter at all (difference will not be measurable in normal cases).
假设有一个现代的编译器/stl/平台,并不是每天都能更有效地使用new和delete。对于shared_ptr,我认为它是边际的。在一个非常紧密的循环(s)获得的好处可能是使用原始新避免裁判计数(并找到其他的方法清理——除非强加给你,你选择要查看的原因),但这不是一个日常或常见的例子。对于unique_ptr实际上没有任何差异,所以我认为可以肯定地说,它更多的是谣言和民间传说,而性能方面的智慧实际上根本不重要(差异在正常情况下是无法衡量的)。
There are cases where it is not desirable or possible to use a smart pointer class as already covered by others.
有些情况下,使用智能指针类是不可取的,或者是不可能的。
#5
4
Some APIs might expect you to create objects with new
but will take over ownership of the object. The Qt library for example has a parent-child model where the parent deletes its children. If you use a smart pointer, you are going to run into double-deletion issues if you're not careful.
一些api可能希望您创建具有新属性的对象,但将接管对象的所有权。例如,Qt库有一个父/子模型,父/子模型删除它的子模型。如果您使用一个智能指针,如果不小心的话,您将会遇到双删除问题。
Example:
例子:
{
// parentWidget has no parent.
QWidget parentWidget(nullptr);
// childWidget is created with parentWidget as parent.
auto childWidget = new QWidget(&parentWidget);
}
// At this point, parentWidget is destroyed and it deletes childWidget
// automatically.
In this particular example, you can still use a smart pointer and it will be fine:
在这个特别的例子中,你仍然可以使用一个智能指针,这是可以的:
{
QWidget parentWidget(nullptr);
auto childWidget = std::make_unique<QWidget>(&parentWidget);
}
because objects are destroyed in reverse order of declaration. unique_ptr
will delete childWidget
first, which will make childWidget
unregister itself from parentWidget
and thus avoid double-deletion. However, most of the time you don't have that neatness. There are many situations where the parent will be destroyed first, and in those cases, the children will get deleted twice.
因为对象的销毁顺序与声明顺序相反。unique_ptr将首先删除childWidget,这将使childWidget从parentWidget中取消注册,从而避免重复删除。然而,大多数时候你并没有那么整洁。在许多情况下,父类首先被销毁,在这些情况下,子类将被删除两次。
In the above case, we own the parent in that scope, and thus have full control of the situation. In other cases, the parent might not be hours, but we're handing ownership of our child widget to that parent, which lives somewhere else.
在上面的例子中,我们在这个范围内拥有父类,因此可以完全控制这种情况。在其他情况下,父窗口可能不是小时,但是我们将我们的子窗口小部件的所有权交给了位于其他地方的父窗口。
You might be thinking that to solve this, you just have to avoid the parent-child model and create all your widgets on the stack and without a parent:
您可能认为要解决这个问题,您只需避免父-子模型,并在堆栈上创建所有小部件,而不需要父类:
QWidget childWidget(nullptr);
or with a smart pointer and without a parent:
或者有一个智能指针,没有父母:
auto childWidget = std::make_unique<QWidget>(nullptr);
However, this will blow up in your face too, since once you start using the widget, it might get re-parented behind your back. Once another object becomes the parent, you get double-deletion when using unique_ptr
, and stack deletion when creating it on the stack.
然而,这也会在你的脸上炸开,因为一旦你开始使用这个小部件,它可能会在你背后被重新设计。当另一个对象成为父对象时,在使用unique_ptr时,您会得到双删除,并在堆栈上创建它时将其删除。
The easiest way to work with this is to use new
. Anything else is either inviting trouble, or more work, or both.
最简单的方法是使用new。其他任何事情都可能带来麻烦,或带来更多的工作,或两者兼而有之。
Such APIs can be found in modern, non-deprecated software (like Qt), and have been developed years ago, long before smart pointers were a thing. They cannot be changed easily since that would break people's existing code.
这样的api可以在现代的、不被废弃的软件(如Qt)中找到,而且早在智能指针出现之前就已经开发了。它们不能轻易更改,因为这会破坏人们现有的代码。
#6
3
For simple use cases, smart pointers, standard containers and references should be enough to use no pointers and raw allocation and de-allocation.
对于简单的用例,智能指针、标准容器和引用应该足够不使用指针和原始分配和反分配。
Now for the cases I can think about:
现在我可以想想
- development of containers or other low-level concepts - after all the standard library itself is written in C++ and it does make use of raw pointers, new and delete
- 容器或其他低级概念的开发——毕竟标准库本身是用c++编写的,它使用原始指针、新指针和删除
- low level optimization. It should never be a first class concern, because compilers are smart enough to optimize standard code, and maintainability is normally more important than raw performance. But when profiling shows that a block of code represents more than 80% of the execution time, low level optimization makes sense, and thats one of the reasons why the low level C standard library is still a part of C++ standards
- 低水平的优化。它不应该是第一类的问题,因为编译器足够聪明,可以优化标准代码,而且可维护性通常比原始性能更重要。但是当分析显示一个代码块代表超过80%的执行时间时,低级别的优化是有意义的,这也是为什么低级别C标准库仍然是c++标准的一部分的原因之一。
#7
2
Another possible valid use case is when you code some garbage collector.
另一个可能的有效用例是当您编写一些垃圾收集器时。
Imagine that you are coding some Scheme interpreter in C++11 (or some Ocaml bytecode interpreter). That language requires you to code a GC (so you need to code one in C++). So ownership is not local, as answered by Yakk. And you want to garbage collect Scheme values, not raw memory!
假设您正在用c++ 11(或一些Ocaml字节码解释器)编写一些方案解释器。该语言要求您编写GC(因此需要用c++编写GC)。所以所有权不是本地的,正如Yakk回答的那样。您想要垃圾收集方案值,而不是原始内存!
You probably will end up using explicit new
and delete
.
您可能最终会使用显式的new和delete。
In other words, C++11 smart pointers favor some reference counting scheme. But that is a poor GC technique (it is not friendly with circular references, which are common in Scheme).
换句话说,c++ 11智能指针支持一些引用计数方案。但这是一种糟糕的GC技术(它对循环引用不友好,循环引用在Scheme中很常见)。
For example, a naive way of implementing a simple mark-and-sweep GC would be to collect in some global container all the pointers of Scheme values, etc...
例如,实现简单的标记-清除GC的一种简单方法是在一些全局容器中收集所有Scheme值的指针,等等……
Read also the GC handbook.
阅读GC手册。
#8
2
When you have to pass something across the DLL boundary. You (almost) can't do that with smart pointers.
当你需要通过DLL边界时。你(几乎)不能用智能指针做到这一点。
#9
2
3 common examples where you have to use new instead of make_...
:
3个常见的例子,你必须使用新的而不是make_…:
- If your object doesn't have a public constructor
- 如果对象没有公共构造函数
- If you want to use a custom deleter
- 如果您想使用自定义删除程序
- If you are using c++11 and want to create an object that is managed by a unique_ptr (athough I'd recommend writing your own
make_unique
in that case). - 如果您正在使用c++11,并希望创建一个由unique_ptr管理的对象(在这种情况下,我建议您编写自己的make_unique)。
In all those cases however, you'd directly wrap the returned pointer into a smart pointer.
但是,在所有这些情况下,您将直接将返回的指针包装成一个智能指针。
2-3 (probably not so common) examples, where you wouldn't want/can't to use smart pointers:
2-3个(可能不太常见)的例子,在这些例子中,你不希望/不能使用智能指针:
- If you have to pass your types through a c-api (you are the one implementing
create_my_object
or implement a callback that has to take a void*) - 如果您必须通过c-api传递类型(您是实现create_my_object的人,或者实现一个必须采用void*的回调)
- Cases of conditional ownership: Think of a string, that doesn't allocate memory when it is created from a string litteral but just points to that data. Nowerdays you probably could use a
std::variant<T*, unique_ptr<T>>
instead, but only if you are ok with the the information about the ownership being stored in the variant and is you accept the overhead of checking which member is active for each access. Of course this is only relevant if you can't/don't want to afford the overhead of having two pointers (one owning and one non-owning)- If you want to base your ownership on anything more complex than a pointer. E.g. you want to use a gsl::owner so you can easily query it's size and have all the other goodies (iteration, rangecheck...). Admittedly, you'd most likely wrap that in your own class,so this might fall into the category of implementing a container.
- 如果你想把你的所有权建立在比指针更复杂的东西上。例如,您想要使用gsl::owner,以便您可以轻松地查询它的大小,并拥有所有其他的优点(迭代,rangecheck…)。不可否认,您很可能将其封装在自己的类中,因此这可能属于实现容器的类别。
-
条件所有权的情况:考虑一个字符串,当它从字符串litteral创建时,它不会分配内存,而是指向该数据。现在,您可能可以使用std::variant
>,但前提是您对存储在该变体中的所有权信息没有问题,并且您接受检查每个访问哪个成员是活动的开销。当然,只有当您不能/不想承担拥有两个指针(一个拥有一个不拥有)的开销时,这才有意义,如果您想将您的所有权建立在比指针更复杂的东西上的话。例如,您想要使用gsl::owner,以便您可以轻松地查询它的大小,并拥有所有其他的优点(迭代,rangecheck…)。不可否认,您很可能将其封装在自己的类中,因此这可能属于实现容器的类别。 *,>
#10
2
You sometimes have to call new when using private constructors.
在使用私有构造函数时,有时必须调用new。
Say you decide to have a private constructor for a type that is intended to be called by a friend factory or an explicit create method. You can call new
inside this factory but make_unique
won't work.
假设您决定为某个类型拥有一个私有构造函数,该类型将被朋友工厂或显式创建方法调用。您可以在这个工厂中调用new,但是make_unique不能工作。
#11
2
Adding to other answers, there are some cases where new/delete make sense -
除了其他答案之外,还有一些情况下new/delete是有意义的
- Integrating with a 3rd party library which returns the raw pointer and expect you return the pointer to the library once you are done (The library has its own memory management functionality).
- 与第三方库集成,该库返回原始指针,并期望在完成之后返回指向库的指针(库有自己的内存管理功能)。
- Working on resource constrained embedded device where memory (RAM/ROM) is a luxury (even a few kilobytes). Are you sure you want to add more runtime (RAM) and compiled (ROM/Overlay) memory requirement to your application or you want to program carefully with new/delete?
- 在资源受限的嵌入式设备上工作,内存(RAM/ROM)是一种奢侈(甚至是几千字节)。您确定要向应用程序添加更多的运行时(RAM)和编译(ROM/叠加)内存需求,还是要小心地使用new/delete进行编程?
- From purist point of view, in some cases smart pointers won't work intuitively (due to their nature). For example, for builder pattern you should to use reinterpret_pointer_cast, if you are using smart pointers. Another case is where you need to cast from a base type to a derived type. You put yourself danger if you get the raw pointer from smart pointer, cast it and put it in another smart pointer and ended up freeing the pointer multiple times.
- 从纯粹的观点来看,在某些情况下,智能指针不会直观地工作(由于它们的性质)。例如,对于构建器模式,如果您正在使用智能指针,您应该使用reinterpret_pointer_cast。另一种情况是需要将基类型转换为派生类型。如果你从智能指针中获取原始指针,那么你就有危险了,将它转换成另一个智能指针,最后多次释放指针。
#12
2
One of the problem I deal with is mining big data structures for hardware design and language analysis with few hundred million elements. Memory usage and performance is a consideration.
我要解决的问题之一是挖掘大数据结构,用数亿个元素进行硬件设计和语言分析。内存使用和性能是一个考虑因素。
Containers are a good convenient way to quickly assemble data and work with it, but the implementation uses extra memory and extra dereferences which affect both, the memory and performance. My recent experiment with replacing smart pointers with a different custom implementation provided about 20% performance gain in a verilog preprocessor. Few years ago I did compare custom lists and custom trees vs vectors/maps and also saw gains. The custom implementations rely on regular new/delete.
容器是快速组装和使用数据的一种很方便的方式,但是实现使用了额外的内存和额外的取消引用,这将影响内存和性能。我最近用不同的自定义实现替换智能指针的实验在verilog预处理器中提供了大约20%的性能提升。几年前,我比较了自定义列表和自定义树与向量/映射,也看到了收益。自定义实现依赖于常规的new/delete。
So, new/delete are useful in high-efficiency applications for custom designed data structs.
因此,新/删除对于定制设计的数据结构的高效应用程序非常有用。
#13
1
You can still use new
and delete
if we want to create our own lightweight memory allocation mechanism. For example
如果我们想创建自己的轻量级内存分配机制,您仍然可以使用new和delete。例如
1.Using In-Place new : Generally used for allocating from preallocated memory;
1。使用就地新:通常用于从预先分配的内存中进行分配;
char arr[4];
int * intVar = new (&arr) int; // assuming int of size 4 bytes
2.Using Class Specific Allocators : If we want a custom allocator for our own classes.
2。使用类特定的分配器:如果我们想要为我们自己的类定制分配器。
class AwithCustom {
public:
void * operator new(size_t size) {
return malloc(size);
}
void operator delete(void * ptr) {
free(ptr);
}
};
#14
1
The primary use case where I still use raw pointers is when implementing a hierarchy that uses covariant return types.
我仍然使用原始指针的主要用例是在实现使用协变返回类型的层次结构时。
For example:
例如:
#include <iostream>
#include <memory>
class Base
{
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Foo : public Base
{
public:
~Foo() override {}
// Case A in main wouldn't work if this returned `Base*`
Foo* clone() const override { return new Foo(); }
};
class Bar : public Base
{
public:
~Bar() override {}
// Case A in main wouldn't work if this returned `Base*`
Bar* clone() const override { return new Bar(); }
};
int main()
{
Foo defaultFoo;
Bar defaultBar;
// Case A: Can maintain the same type when cloning
std::unique_ptr<Foo> fooCopy(defaultFoo.clone());
std::unique_ptr<Bar> barCopy(defaultBar.clone());
// Case B: Of course cloning to a base type still works
std::unique_ptr<Base> base1(fooCopy->clone());
std::unique_ptr<Base> base2(barCopy->clone());
return 0;
}
#15
1
There is still a chance to use malloc/free
in C++, as you can use new/delete
, and anything higher level wrapping the STL
memory templates provided.
仍然有机会在c++中使用malloc/free,因为您可以使用new/delete和任何更高级别的包装提供的STL内存模板。
I think in order to really learn C++ and especially understand the C++11 memory templates you should create simple structures with new
and delete
. Just to better understand how they work. All the smart pointer classes rely on those mechanisms. So if you understand what new
and delete
does, you are going to appreciate the template more and really find smart ways to use them.
我认为为了真正地学习c++,特别是理解c++的内存模板,你应该用new和delete创建简单的结构。只是为了更好地理解它们是如何工作的。所有的智能指针类都依赖于这些机制。因此,如果您理解了新和删除的功能,您将更加欣赏模板,并真正找到使用它们的智能方法。
Today I personally try to avoid them as much as possible, but one main reason is the performance, which you should care if it is critical.
今天,我个人尽量避免使用它们,但一个主要原因是性能,如果性能是关键的,您应该注意这一点。
These are my rules of thumb I always have in mind:
这是我一直牢记的经验法则:
std::shared_ptr
: Automatic management of pointers but due to the reference counting it uses for tracing the accessed pointers, you have a worse performance every time you access these objects. Compared simple pointers I would say 6 times slower. Keep in mind, you can use get()
and extract the primitive pointer, and continue accessing it. Of you must be careful with that one. I like to as that as a reference with *get()
, so the worse performance is not really a deal.
std::shared_ptr:指针的自动管理,但是由于它用于跟踪被访问指针的引用计数,每次访问这些对象时性能都会更差。与简单指针相比,我认为慢6倍。记住,您可以使用get()并提取原始指针,并继续访问它。你得小心点。我喜欢把它作为*get()的引用,因此更差的性能并不是真正的问题。
std::unique_ptr
The pointer access may only happen at one point in the code. Because this template forbids copy, thanks to the r-references &&
feature, it is much faster than an std::shared_ptr
. Because there is still some ownership overhead in this class I would say, they are about twice as slow as a primitive pointer. You access the object than the primitive pointer within that template. I also like to use reference trick here, for less required accesses to the object.
unique_ptr指针访问可能只发生在代码的某一点。由于这个模板禁止复制,由于r-references &特性,它比std: shared_ptr要快得多。因为在这个类中还有一些所有权开销,我想说,它们的速度是原始指针的两倍。您将访问该对象,而不是该模板内的原始指针。我也喜欢在这里使用引用技巧,以减少对对象的访问。
About performance, it might be true, that those templates are slower, but keep in mind that if you want to optimize software, you should profile first and see what really takes many instructions. It is very unlikely that smart-pointers are the problem, but sure it depends on your implementation.
关于性能,这些模板可能是比较慢的,但是请记住,如果您想优化软件,您应该首先对其进行概要分析,看看需要多少指令。问题不太可能是智能指针,但这取决于您的实现。
In C++ no one should care about malloc
and free
, but they exist for legacy code. They differ basically in the fact, that they know nothing about c++ classes, which with new
and delete
operator case is different.
在c++中,没有人应该关心malloc和free,但是它们存在于遗留代码中。它们的基本不同之处在于,它们对c++类一无所知,而使用new和delete操作符则不同。
I use std::unique_ptr
and std::shared_ptr
in my project Commander Genius everywhere and I'm really happy that they exist. I have not to deal with memory leaks and segfaults since then. Before that, we had our own smart-pointer template. So for productive software, I cannot recommend them enough.
我在我的项目指挥官Genius everywhere中使用了std: unique_ptr和std::shared_ptr,我很高兴它们的存在。从那以后,我就不用处理内存泄漏和分段错误了。在此之前,我们有自己的智能指针模板。因此对于生产软件来说,我无法推荐足够的软件。
#16
0
Another use case may be 3rd party library returning raw pointer which is internally covered by own intrusive reference counting (or own memory management - which is not covered by any API/user interface).
另一个用例可能是第三方库返回原始指针,它在内部被自己的入侵引用计数(或自己的内存管理——任何API/用户界面都不包括)覆盖。
Good example is OpenSceneGraph and their implementation of osg::ref_ptr container and osg::Referenced base class.
一个很好的例子是OpenSceneGraph及其实现osg: ref_ptr容器和osg::引用基类。
Although it may be possible to use shared_ptr, the intrusive reference counting is way better for scene graph like use cases.
虽然可以使用shared_ptr,但是对于场景图(如用例)来说,介入式引用计数要好得多。
Personally I do see anything "smart" on the unique_ptr. It is just scope locked new & delete. Although shared_ptr looks way better, it requires overhead which is in many practical cases unacceptable.
就我个人而言,我确实在unique_ptr上看到了任何“聪明”的东西。它只是锁定的范围new & delete。尽管shared_ptr看起来更好,但它需要开销,这在许多实际情况下是不可接受的。
So in general my use case is:
总的来说,我的用例是:
When dealing with non-STL raw pointer wrappers.
在处理非stl原始指针包装时。
#17
0
another example that has not already been mentioned is when you need to pass an object through a legacy (possibly asynchronous) C-callback. Usually, these things take a function pointer and a void* (or an opaque handle) to pass some payload upon. As long as the callback gives some guarantee on when/how/how many times it will be invoked, resorting to a plain new->cast->callback->cast->delete is the most straightforward solution (ok, the delete will be probably managed by a unique_ptr on callback site, but the bare new is still there). Of course, alternative solutions exist, but always requires the implementation of some sort of explicit/implicit 'object lifetime manager' in that case.
另一个还没有提到的例子是,当您需要通过遗留(可能是异步)C-callback传递对象时。通常,这些东西使用一个函数指针和一个void*(或一个不透明的句柄)来传递一些负载。只要回调提供了一些关于何时/如何/多少次将被调用的保证,求助于一个简单的新方法——>cast->callback——>cast->delete是最简单的解决方案(好吧,这个delete很可能由回调站点上的unique_ptr管理,但是这个新方法仍然存在)。当然,还有其他的解决方案,但是在这种情况下,总是需要实现某种显式/隐式的“对象生存期管理器”。
#18
-1
I think this is typically a good use case and/or guideline to follow:
我认为这通常是一个很好的用例和/或指导方针:
- When the pointer is local to single function's scope.
- 当指针位于单个函数的作用域时。
- The dynamic memory is handled in the function and you need the heap.
- 动态内存在函数中处理,需要堆。
- You are not passing the pointer around and it isn't leaving the functions scope.
- 您没有传递指针,它也没有离开函数作用域。
PSEUDO Code:
伪代码:
#include <SomeImageLibrary>
// Texture is a class or struct defined somewhere else.
unsigned funcToOpenAndLoadImageData( const std::string& filenameAndPath, Texture& texture, some optional flags (how to process or handle within function ) {
// Depending on the above library: file* or iostream...
// 1. OpenFile
// 2. Read In Header
// 3. Process Header
// 4. setup some local variables.
// 5. extract basic local variables from the header
// A. texture width, height, bits per pixel, orientation flags, compression flags etc.
// 6. Do some calculations based on the above to find out how much data there is for the actual ImageData...
// 7. Raw pointer (typically of unsigned char).
// 8. Create dynamic memory for that pointer or array.
// 9. Read in the information from the file of that amount into the pointer - array.
// 10. Verify you have all the information.
// 11. Close the file handle.
// 12. Process some more information on the actual pointer or array itself
// based on its orientation, its bits per pixel, its dimensions, the color type, the compression type, and or if it exists encryption type.
// 13. Store the modified data from the array into Your Structure (Texture - Class/Struct).
// 14. Free up dynamic memory...
// 15. typically return the texture through the parameter list as a reference
// 16. typically return an unsigned int as the Texture's numerical ID.
}
This is quite effective; efficient, doesn't need any use of smart pointers; is fast especially if inlining the function. This type of function can either be a stand alone or even a member of a class. If a pattern follows this then it is quite safe to use new & delete or new[] & delete[] if done properly.
这是很有效的;高效,不需要任何智能指针;是快速的,特别是如果内联功能。这种类型的函数可以是独立的,也可以是类的成员。如果一个模式遵循这一点,那么如果操作得当,使用new & delete或new[] & delete[]是相当安全的。
EDIT
编辑
In the mentioned case(s) above sometimes you want the raw pointers and you want it on the heap. Let's say you have an application that will load say 5,000 texture files, 500 model files, 20 scene files, 500-1000 audio files. You do not want your loading time to be slow, you also want it to be "cache" friendly. Texture loading is very good example of having the pointer on the heap as opposed to the functions stack because the texture could be large in size exceeding your local memory capabilities.
在上面提到的情况中,有时您需要原始指针并希望它位于堆中。假设你有一个应用程序,它会加载5000个纹理文件,500个模型文件,20个场景文件,500-1000个音频文件。您不希望加载时间变慢,您还希望它对“缓存”友好。纹理加载是堆上指针而不是函数堆栈的一个很好的例子,因为纹理的大小可能超过您的本地内存能力。
In this context you will be calling these load functions once per object, but you will be calling them several times. After you loaded & created your resources or assets and stored them internally is when and where you would want to use containers instead of arrays and smart pointers instead of raw pointers.
在这个上下文中,您将为每个对象调用一次这些load函数,但是您将多次调用它们。在您加载和创建资源或资产并在内部存储它们之后,您将希望使用容器而不是数组和智能指针,而不是原始指针。
You will load a single asset once, but you may have 100s or 1000s of instances of it. It is with these instances that you would prefer the use of containers and the use of smart pointers to manage their memory within your application over raw pointers and arrays. The initial loading is where you would prefer to be closer to the metal without all the extra unwanted overhead.
您将加载单个资产一次,但是您可能有100个或1000个这样的实例。通过这些实例,您更希望使用容器和智能指针来管理应用程序中的内存,而不是原始指针和数组。最初的负载是你希望更接近金属而不需要额外的开销。
If you were working on a A+ class game and you could save your audience 15 to 30s or more of loading time per loading screen then you are in the winners circle. Yes care does need to be taken and yes you can still have unhandled exceptions, but no code is 100% full proof.
如果你正在做一个a +类游戏,你可以为你的观众节省15到30秒甚至更多的加载时间,那么你就进入了赢家圈。是的,确实需要小心,是的,您仍然可以有未处理的异常,但是没有代码是100%完全证明的。
This type of design is rarely prone to memory leaks except for those exceptions which can still be handled in many of the cases. Also to safely manage raw pointers, preprocessor macros work well for easy clean up.
这种类型的设计很少容易出现内存泄漏,除了那些在许多情况下仍然可以处理的异常。此外,为了安全地管理原始指针,预处理器宏可以很好地进行清理。
Many of these library types also work and deal with raw data
, raw memory allocation
, etc. and many times smart pointers don't necessarily fit these types of jobs.
许多此类库类型还可以处理原始数据、原始内存分配等,而且很多时候,智能指针并不一定适合这些类型的作业。
#19
-2
When you want to create multidimensional arrays but aren't familiar with C++11 syntax like std::move, or aren't familiar with writing custom deleters for smart pointers.
如果您希望创建多维数组,但不熟悉std::move等c++ 11语法,或者不熟悉为智能指针编写自定义删除程序。
#1
24
When ownership should not be local.
当所有权不应该是本地的时候。
As an example, a pointer container may not want ownership over the pointers in it to reside in the pointers themselves. If you try to write a linked list with forward unique ptrs, at destruction time you can easily blow the stack.
作为一个例子,指针容器可能不希望对它的指针拥有所有权,因为它驻留在指针本身中。如果您试图用唯一的ptrs写一个链表,在销毁时,您可以很容易地销毁堆栈。
A vector
-like container of owning pointers may be better suited to storing delete operation at the container or subcontainer level, and not at the element level.
拥有指针的矢量容器可能更适合在容器或子容器级别存储删除操作,而不是在元素级别。
In those and similar cases, you wrap ownership like a smart pointer does, but you do it at a higher level. Many data structures (graphs, etc) may have similar issues, where ownership properly resides at a higher point than where the pointers are, and they may not map directly to an existing container concept.
在这些和类似的情况下,您可以像智能指针那样封装所有权,但您是在更高的级别上进行的。许多数据结构(图等)可能有类似的问题,所有权正确地驻留在比指针所在的位置更高的位置,并且它们可能不会直接映射到现有容器概念。
In some cases it may be easy to factor out the container-ownership from the rest of the data structure. In others it may not.
在某些情况下,很容易将容器所有权从数据结构的其他部分中分离出来。在另一些国家,情况可能并非如此。
Sometimes you have insanely complex non-local non-reference counted lifetimes. There is no sane spot to put the ownership pointer in those cases.
有时你会有非常复杂的非本地非引用计数寿命。在这些情况下,没有理由把所有权指针放在那里。
Determining correctness here is hard, but not impossible. Programs that are correct and have such complex ownership semantics exist.
在这里确定正确性很困难,但并非不可能。存在正确且具有如此复杂所有权语义的程序。
All of these are corner cases, and few programmers should run into them more than a handful of times in a career.
所有这些都是一边倒的情况,很少有程序员在职业生涯中遇到过这样的情况。
#2
14
I'm going to be contrarian, and go on record as saying "no" (at least to the question I'm pretty sure you really intended to ask, for most of the cases that have been cited).
我将持相反意见,并公开地说“不”(至少我很确定你真的想问这个问题,对于大多数已经被引用的案例来说)。
What seem like obvious use-cases for using new
and delete
(e.g., raw memory for a GC heap, storage for a container) really aren't. For these cases, you want "raw" storage, not an object (or array of objects, which is what new
and new[]
provide respectively).
使用new和delete(例如,GC堆的原始内存,容器的存储)的明显用例实际上不是这样的。对于这些情况,您需要的是“原始”存储,而不是对象(或对象数组,它们分别是new和new[]提供的)。
Since you want raw storage, you really need/want to use operator new
and operator delete
to manage the raw storage itself. You then use placement new
to create objects in that raw storage, and directly invoke the destructor to destroy the objects. Depending on the situation, you might want to use a level of indirection to that though--for example, the containers in the standard library use an Allocator class to handle these tasks. This is passed as a template parameter, which provides a customization point (e.g., a way to optimize allocation based on a particular container's typical usage pattern).
由于您需要原始存储,所以您确实需要/希望使用操作符new和操作符delete来管理原始存储本身。然后使用placement new在原始存储中创建对象,并直接调用析构函数来销毁对象。根据具体情况,您可能需要使用一种间接的方式—例如,标准库中的容器使用分配器类来处理这些任务。它作为模板参数传递,提供一个定制点(例如,根据特定容器的典型使用模式优化分配的方法)。
So, for these situations, you end up using the new
keyword (in both the placement new and the invocation of operator new
), but not something like T *t = new T[N];
, which is what I'm pretty sure you intended to ask about.
因此,在这些情况下,您最终会使用new关键字(在放置new和调用operator new中),但不会使用T * T = new T[N];
#3
11
One valid use case is having to interact with legacy code. Especially if passing raw pointers to functions that take ownership of them.
一个有效的用例是必须与遗留代码交互。特别是如果将原始指针传递给拥有它们的函数。
Not all libraries you use may be using smart pointers and to use them you may need to provide or accept raw pointers and manage their lifetimes manually. This may even be the case within your own codebase if it has a long history.
并不是您使用的所有库都使用智能指针,要使用它们,您可能需要提供或接受原始指针并手动管理它们的生存期。如果您自己的代码库有很长的历史,这甚至可能是这样。
Another use case is having to interact with C which does not have smart pointers.
另一个用例是必须与没有智能指针的C交互。
#4
4
The OP specificly asks about how/when handrolling will be more efficient in an everyday use case - and I will address that.
OP专门询问如何/何时在日常用例中更有效地进行移植——我将对此进行说明。
Assuming a modern day compiler/stl/platform, there is not an every day use where handrolled use of new and delete will be more efficient. For the shared_ptr case i believe it will be marginal. In an extremely tight loop(s) there could be something to gain by just using raw new to avoid the ref counting (and find some other method of cleaning up - unless somehow imposed on you, you choose to use shared_ptr for a reason), but that is not an everyday or common example. For the unique_ptr there is not actually any difference, so i think it is safe to say that it is more of rumour and folklore and that performance wise it will not actually matter at all (difference will not be measurable in normal cases).
假设有一个现代的编译器/stl/平台,并不是每天都能更有效地使用new和delete。对于shared_ptr,我认为它是边际的。在一个非常紧密的循环(s)获得的好处可能是使用原始新避免裁判计数(并找到其他的方法清理——除非强加给你,你选择要查看的原因),但这不是一个日常或常见的例子。对于unique_ptr实际上没有任何差异,所以我认为可以肯定地说,它更多的是谣言和民间传说,而性能方面的智慧实际上根本不重要(差异在正常情况下是无法衡量的)。
There are cases where it is not desirable or possible to use a smart pointer class as already covered by others.
有些情况下,使用智能指针类是不可取的,或者是不可能的。
#5
4
Some APIs might expect you to create objects with new
but will take over ownership of the object. The Qt library for example has a parent-child model where the parent deletes its children. If you use a smart pointer, you are going to run into double-deletion issues if you're not careful.
一些api可能希望您创建具有新属性的对象,但将接管对象的所有权。例如,Qt库有一个父/子模型,父/子模型删除它的子模型。如果您使用一个智能指针,如果不小心的话,您将会遇到双删除问题。
Example:
例子:
{
// parentWidget has no parent.
QWidget parentWidget(nullptr);
// childWidget is created with parentWidget as parent.
auto childWidget = new QWidget(&parentWidget);
}
// At this point, parentWidget is destroyed and it deletes childWidget
// automatically.
In this particular example, you can still use a smart pointer and it will be fine:
在这个特别的例子中,你仍然可以使用一个智能指针,这是可以的:
{
QWidget parentWidget(nullptr);
auto childWidget = std::make_unique<QWidget>(&parentWidget);
}
because objects are destroyed in reverse order of declaration. unique_ptr
will delete childWidget
first, which will make childWidget
unregister itself from parentWidget
and thus avoid double-deletion. However, most of the time you don't have that neatness. There are many situations where the parent will be destroyed first, and in those cases, the children will get deleted twice.
因为对象的销毁顺序与声明顺序相反。unique_ptr将首先删除childWidget,这将使childWidget从parentWidget中取消注册,从而避免重复删除。然而,大多数时候你并没有那么整洁。在许多情况下,父类首先被销毁,在这些情况下,子类将被删除两次。
In the above case, we own the parent in that scope, and thus have full control of the situation. In other cases, the parent might not be hours, but we're handing ownership of our child widget to that parent, which lives somewhere else.
在上面的例子中,我们在这个范围内拥有父类,因此可以完全控制这种情况。在其他情况下,父窗口可能不是小时,但是我们将我们的子窗口小部件的所有权交给了位于其他地方的父窗口。
You might be thinking that to solve this, you just have to avoid the parent-child model and create all your widgets on the stack and without a parent:
您可能认为要解决这个问题,您只需避免父-子模型,并在堆栈上创建所有小部件,而不需要父类:
QWidget childWidget(nullptr);
or with a smart pointer and without a parent:
或者有一个智能指针,没有父母:
auto childWidget = std::make_unique<QWidget>(nullptr);
However, this will blow up in your face too, since once you start using the widget, it might get re-parented behind your back. Once another object becomes the parent, you get double-deletion when using unique_ptr
, and stack deletion when creating it on the stack.
然而,这也会在你的脸上炸开,因为一旦你开始使用这个小部件,它可能会在你背后被重新设计。当另一个对象成为父对象时,在使用unique_ptr时,您会得到双删除,并在堆栈上创建它时将其删除。
The easiest way to work with this is to use new
. Anything else is either inviting trouble, or more work, or both.
最简单的方法是使用new。其他任何事情都可能带来麻烦,或带来更多的工作,或两者兼而有之。
Such APIs can be found in modern, non-deprecated software (like Qt), and have been developed years ago, long before smart pointers were a thing. They cannot be changed easily since that would break people's existing code.
这样的api可以在现代的、不被废弃的软件(如Qt)中找到,而且早在智能指针出现之前就已经开发了。它们不能轻易更改,因为这会破坏人们现有的代码。
#6
3
For simple use cases, smart pointers, standard containers and references should be enough to use no pointers and raw allocation and de-allocation.
对于简单的用例,智能指针、标准容器和引用应该足够不使用指针和原始分配和反分配。
Now for the cases I can think about:
现在我可以想想
- development of containers or other low-level concepts - after all the standard library itself is written in C++ and it does make use of raw pointers, new and delete
- 容器或其他低级概念的开发——毕竟标准库本身是用c++编写的,它使用原始指针、新指针和删除
- low level optimization. It should never be a first class concern, because compilers are smart enough to optimize standard code, and maintainability is normally more important than raw performance. But when profiling shows that a block of code represents more than 80% of the execution time, low level optimization makes sense, and thats one of the reasons why the low level C standard library is still a part of C++ standards
- 低水平的优化。它不应该是第一类的问题,因为编译器足够聪明,可以优化标准代码,而且可维护性通常比原始性能更重要。但是当分析显示一个代码块代表超过80%的执行时间时,低级别的优化是有意义的,这也是为什么低级别C标准库仍然是c++标准的一部分的原因之一。
#7
2
Another possible valid use case is when you code some garbage collector.
另一个可能的有效用例是当您编写一些垃圾收集器时。
Imagine that you are coding some Scheme interpreter in C++11 (or some Ocaml bytecode interpreter). That language requires you to code a GC (so you need to code one in C++). So ownership is not local, as answered by Yakk. And you want to garbage collect Scheme values, not raw memory!
假设您正在用c++ 11(或一些Ocaml字节码解释器)编写一些方案解释器。该语言要求您编写GC(因此需要用c++编写GC)。所以所有权不是本地的,正如Yakk回答的那样。您想要垃圾收集方案值,而不是原始内存!
You probably will end up using explicit new
and delete
.
您可能最终会使用显式的new和delete。
In other words, C++11 smart pointers favor some reference counting scheme. But that is a poor GC technique (it is not friendly with circular references, which are common in Scheme).
换句话说,c++ 11智能指针支持一些引用计数方案。但这是一种糟糕的GC技术(它对循环引用不友好,循环引用在Scheme中很常见)。
For example, a naive way of implementing a simple mark-and-sweep GC would be to collect in some global container all the pointers of Scheme values, etc...
例如,实现简单的标记-清除GC的一种简单方法是在一些全局容器中收集所有Scheme值的指针,等等……
Read also the GC handbook.
阅读GC手册。
#8
2
When you have to pass something across the DLL boundary. You (almost) can't do that with smart pointers.
当你需要通过DLL边界时。你(几乎)不能用智能指针做到这一点。
#9
2
3 common examples where you have to use new instead of make_...
:
3个常见的例子,你必须使用新的而不是make_…:
- If your object doesn't have a public constructor
- 如果对象没有公共构造函数
- If you want to use a custom deleter
- 如果您想使用自定义删除程序
- If you are using c++11 and want to create an object that is managed by a unique_ptr (athough I'd recommend writing your own
make_unique
in that case). - 如果您正在使用c++11,并希望创建一个由unique_ptr管理的对象(在这种情况下,我建议您编写自己的make_unique)。
In all those cases however, you'd directly wrap the returned pointer into a smart pointer.
但是,在所有这些情况下,您将直接将返回的指针包装成一个智能指针。
2-3 (probably not so common) examples, where you wouldn't want/can't to use smart pointers:
2-3个(可能不太常见)的例子,在这些例子中,你不希望/不能使用智能指针:
- If you have to pass your types through a c-api (you are the one implementing
create_my_object
or implement a callback that has to take a void*) - 如果您必须通过c-api传递类型(您是实现create_my_object的人,或者实现一个必须采用void*的回调)
- Cases of conditional ownership: Think of a string, that doesn't allocate memory when it is created from a string litteral but just points to that data. Nowerdays you probably could use a
std::variant<T*, unique_ptr<T>>
instead, but only if you are ok with the the information about the ownership being stored in the variant and is you accept the overhead of checking which member is active for each access. Of course this is only relevant if you can't/don't want to afford the overhead of having two pointers (one owning and one non-owning)- If you want to base your ownership on anything more complex than a pointer. E.g. you want to use a gsl::owner so you can easily query it's size and have all the other goodies (iteration, rangecheck...). Admittedly, you'd most likely wrap that in your own class,so this might fall into the category of implementing a container.
- 如果你想把你的所有权建立在比指针更复杂的东西上。例如,您想要使用gsl::owner,以便您可以轻松地查询它的大小,并拥有所有其他的优点(迭代,rangecheck…)。不可否认,您很可能将其封装在自己的类中,因此这可能属于实现容器的类别。
-
条件所有权的情况:考虑一个字符串,当它从字符串litteral创建时,它不会分配内存,而是指向该数据。现在,您可能可以使用std::variant
>,但前提是您对存储在该变体中的所有权信息没有问题,并且您接受检查每个访问哪个成员是活动的开销。当然,只有当您不能/不想承担拥有两个指针(一个拥有一个不拥有)的开销时,这才有意义,如果您想将您的所有权建立在比指针更复杂的东西上的话。例如,您想要使用gsl::owner,以便您可以轻松地查询它的大小,并拥有所有其他的优点(迭代,rangecheck…)。不可否认,您很可能将其封装在自己的类中,因此这可能属于实现容器的类别。 *,>
#10
2
You sometimes have to call new when using private constructors.
在使用私有构造函数时,有时必须调用new。
Say you decide to have a private constructor for a type that is intended to be called by a friend factory or an explicit create method. You can call new
inside this factory but make_unique
won't work.
假设您决定为某个类型拥有一个私有构造函数,该类型将被朋友工厂或显式创建方法调用。您可以在这个工厂中调用new,但是make_unique不能工作。
#11
2
Adding to other answers, there are some cases where new/delete make sense -
除了其他答案之外,还有一些情况下new/delete是有意义的
- Integrating with a 3rd party library which returns the raw pointer and expect you return the pointer to the library once you are done (The library has its own memory management functionality).
- 与第三方库集成,该库返回原始指针,并期望在完成之后返回指向库的指针(库有自己的内存管理功能)。
- Working on resource constrained embedded device where memory (RAM/ROM) is a luxury (even a few kilobytes). Are you sure you want to add more runtime (RAM) and compiled (ROM/Overlay) memory requirement to your application or you want to program carefully with new/delete?
- 在资源受限的嵌入式设备上工作,内存(RAM/ROM)是一种奢侈(甚至是几千字节)。您确定要向应用程序添加更多的运行时(RAM)和编译(ROM/叠加)内存需求,还是要小心地使用new/delete进行编程?
- From purist point of view, in some cases smart pointers won't work intuitively (due to their nature). For example, for builder pattern you should to use reinterpret_pointer_cast, if you are using smart pointers. Another case is where you need to cast from a base type to a derived type. You put yourself danger if you get the raw pointer from smart pointer, cast it and put it in another smart pointer and ended up freeing the pointer multiple times.
- 从纯粹的观点来看,在某些情况下,智能指针不会直观地工作(由于它们的性质)。例如,对于构建器模式,如果您正在使用智能指针,您应该使用reinterpret_pointer_cast。另一种情况是需要将基类型转换为派生类型。如果你从智能指针中获取原始指针,那么你就有危险了,将它转换成另一个智能指针,最后多次释放指针。
#12
2
One of the problem I deal with is mining big data structures for hardware design and language analysis with few hundred million elements. Memory usage and performance is a consideration.
我要解决的问题之一是挖掘大数据结构,用数亿个元素进行硬件设计和语言分析。内存使用和性能是一个考虑因素。
Containers are a good convenient way to quickly assemble data and work with it, but the implementation uses extra memory and extra dereferences which affect both, the memory and performance. My recent experiment with replacing smart pointers with a different custom implementation provided about 20% performance gain in a verilog preprocessor. Few years ago I did compare custom lists and custom trees vs vectors/maps and also saw gains. The custom implementations rely on regular new/delete.
容器是快速组装和使用数据的一种很方便的方式,但是实现使用了额外的内存和额外的取消引用,这将影响内存和性能。我最近用不同的自定义实现替换智能指针的实验在verilog预处理器中提供了大约20%的性能提升。几年前,我比较了自定义列表和自定义树与向量/映射,也看到了收益。自定义实现依赖于常规的new/delete。
So, new/delete are useful in high-efficiency applications for custom designed data structs.
因此,新/删除对于定制设计的数据结构的高效应用程序非常有用。
#13
1
You can still use new
and delete
if we want to create our own lightweight memory allocation mechanism. For example
如果我们想创建自己的轻量级内存分配机制,您仍然可以使用new和delete。例如
1.Using In-Place new : Generally used for allocating from preallocated memory;
1。使用就地新:通常用于从预先分配的内存中进行分配;
char arr[4];
int * intVar = new (&arr) int; // assuming int of size 4 bytes
2.Using Class Specific Allocators : If we want a custom allocator for our own classes.
2。使用类特定的分配器:如果我们想要为我们自己的类定制分配器。
class AwithCustom {
public:
void * operator new(size_t size) {
return malloc(size);
}
void operator delete(void * ptr) {
free(ptr);
}
};
#14
1
The primary use case where I still use raw pointers is when implementing a hierarchy that uses covariant return types.
我仍然使用原始指针的主要用例是在实现使用协变返回类型的层次结构时。
For example:
例如:
#include <iostream>
#include <memory>
class Base
{
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Foo : public Base
{
public:
~Foo() override {}
// Case A in main wouldn't work if this returned `Base*`
Foo* clone() const override { return new Foo(); }
};
class Bar : public Base
{
public:
~Bar() override {}
// Case A in main wouldn't work if this returned `Base*`
Bar* clone() const override { return new Bar(); }
};
int main()
{
Foo defaultFoo;
Bar defaultBar;
// Case A: Can maintain the same type when cloning
std::unique_ptr<Foo> fooCopy(defaultFoo.clone());
std::unique_ptr<Bar> barCopy(defaultBar.clone());
// Case B: Of course cloning to a base type still works
std::unique_ptr<Base> base1(fooCopy->clone());
std::unique_ptr<Base> base2(barCopy->clone());
return 0;
}
#15
1
There is still a chance to use malloc/free
in C++, as you can use new/delete
, and anything higher level wrapping the STL
memory templates provided.
仍然有机会在c++中使用malloc/free,因为您可以使用new/delete和任何更高级别的包装提供的STL内存模板。
I think in order to really learn C++ and especially understand the C++11 memory templates you should create simple structures with new
and delete
. Just to better understand how they work. All the smart pointer classes rely on those mechanisms. So if you understand what new
and delete
does, you are going to appreciate the template more and really find smart ways to use them.
我认为为了真正地学习c++,特别是理解c++的内存模板,你应该用new和delete创建简单的结构。只是为了更好地理解它们是如何工作的。所有的智能指针类都依赖于这些机制。因此,如果您理解了新和删除的功能,您将更加欣赏模板,并真正找到使用它们的智能方法。
Today I personally try to avoid them as much as possible, but one main reason is the performance, which you should care if it is critical.
今天,我个人尽量避免使用它们,但一个主要原因是性能,如果性能是关键的,您应该注意这一点。
These are my rules of thumb I always have in mind:
这是我一直牢记的经验法则:
std::shared_ptr
: Automatic management of pointers but due to the reference counting it uses for tracing the accessed pointers, you have a worse performance every time you access these objects. Compared simple pointers I would say 6 times slower. Keep in mind, you can use get()
and extract the primitive pointer, and continue accessing it. Of you must be careful with that one. I like to as that as a reference with *get()
, so the worse performance is not really a deal.
std::shared_ptr:指针的自动管理,但是由于它用于跟踪被访问指针的引用计数,每次访问这些对象时性能都会更差。与简单指针相比,我认为慢6倍。记住,您可以使用get()并提取原始指针,并继续访问它。你得小心点。我喜欢把它作为*get()的引用,因此更差的性能并不是真正的问题。
std::unique_ptr
The pointer access may only happen at one point in the code. Because this template forbids copy, thanks to the r-references &&
feature, it is much faster than an std::shared_ptr
. Because there is still some ownership overhead in this class I would say, they are about twice as slow as a primitive pointer. You access the object than the primitive pointer within that template. I also like to use reference trick here, for less required accesses to the object.
unique_ptr指针访问可能只发生在代码的某一点。由于这个模板禁止复制,由于r-references &特性,它比std: shared_ptr要快得多。因为在这个类中还有一些所有权开销,我想说,它们的速度是原始指针的两倍。您将访问该对象,而不是该模板内的原始指针。我也喜欢在这里使用引用技巧,以减少对对象的访问。
About performance, it might be true, that those templates are slower, but keep in mind that if you want to optimize software, you should profile first and see what really takes many instructions. It is very unlikely that smart-pointers are the problem, but sure it depends on your implementation.
关于性能,这些模板可能是比较慢的,但是请记住,如果您想优化软件,您应该首先对其进行概要分析,看看需要多少指令。问题不太可能是智能指针,但这取决于您的实现。
In C++ no one should care about malloc
and free
, but they exist for legacy code. They differ basically in the fact, that they know nothing about c++ classes, which with new
and delete
operator case is different.
在c++中,没有人应该关心malloc和free,但是它们存在于遗留代码中。它们的基本不同之处在于,它们对c++类一无所知,而使用new和delete操作符则不同。
I use std::unique_ptr
and std::shared_ptr
in my project Commander Genius everywhere and I'm really happy that they exist. I have not to deal with memory leaks and segfaults since then. Before that, we had our own smart-pointer template. So for productive software, I cannot recommend them enough.
我在我的项目指挥官Genius everywhere中使用了std: unique_ptr和std::shared_ptr,我很高兴它们的存在。从那以后,我就不用处理内存泄漏和分段错误了。在此之前,我们有自己的智能指针模板。因此对于生产软件来说,我无法推荐足够的软件。
#16
0
Another use case may be 3rd party library returning raw pointer which is internally covered by own intrusive reference counting (or own memory management - which is not covered by any API/user interface).
另一个用例可能是第三方库返回原始指针,它在内部被自己的入侵引用计数(或自己的内存管理——任何API/用户界面都不包括)覆盖。
Good example is OpenSceneGraph and their implementation of osg::ref_ptr container and osg::Referenced base class.
一个很好的例子是OpenSceneGraph及其实现osg: ref_ptr容器和osg::引用基类。
Although it may be possible to use shared_ptr, the intrusive reference counting is way better for scene graph like use cases.
虽然可以使用shared_ptr,但是对于场景图(如用例)来说,介入式引用计数要好得多。
Personally I do see anything "smart" on the unique_ptr. It is just scope locked new & delete. Although shared_ptr looks way better, it requires overhead which is in many practical cases unacceptable.
就我个人而言,我确实在unique_ptr上看到了任何“聪明”的东西。它只是锁定的范围new & delete。尽管shared_ptr看起来更好,但它需要开销,这在许多实际情况下是不可接受的。
So in general my use case is:
总的来说,我的用例是:
When dealing with non-STL raw pointer wrappers.
在处理非stl原始指针包装时。
#17
0
another example that has not already been mentioned is when you need to pass an object through a legacy (possibly asynchronous) C-callback. Usually, these things take a function pointer and a void* (or an opaque handle) to pass some payload upon. As long as the callback gives some guarantee on when/how/how many times it will be invoked, resorting to a plain new->cast->callback->cast->delete is the most straightforward solution (ok, the delete will be probably managed by a unique_ptr on callback site, but the bare new is still there). Of course, alternative solutions exist, but always requires the implementation of some sort of explicit/implicit 'object lifetime manager' in that case.
另一个还没有提到的例子是,当您需要通过遗留(可能是异步)C-callback传递对象时。通常,这些东西使用一个函数指针和一个void*(或一个不透明的句柄)来传递一些负载。只要回调提供了一些关于何时/如何/多少次将被调用的保证,求助于一个简单的新方法——>cast->callback——>cast->delete是最简单的解决方案(好吧,这个delete很可能由回调站点上的unique_ptr管理,但是这个新方法仍然存在)。当然,还有其他的解决方案,但是在这种情况下,总是需要实现某种显式/隐式的“对象生存期管理器”。
#18
-1
I think this is typically a good use case and/or guideline to follow:
我认为这通常是一个很好的用例和/或指导方针:
- When the pointer is local to single function's scope.
- 当指针位于单个函数的作用域时。
- The dynamic memory is handled in the function and you need the heap.
- 动态内存在函数中处理,需要堆。
- You are not passing the pointer around and it isn't leaving the functions scope.
- 您没有传递指针,它也没有离开函数作用域。
PSEUDO Code:
伪代码:
#include <SomeImageLibrary>
// Texture is a class or struct defined somewhere else.
unsigned funcToOpenAndLoadImageData( const std::string& filenameAndPath, Texture& texture, some optional flags (how to process or handle within function ) {
// Depending on the above library: file* or iostream...
// 1. OpenFile
// 2. Read In Header
// 3. Process Header
// 4. setup some local variables.
// 5. extract basic local variables from the header
// A. texture width, height, bits per pixel, orientation flags, compression flags etc.
// 6. Do some calculations based on the above to find out how much data there is for the actual ImageData...
// 7. Raw pointer (typically of unsigned char).
// 8. Create dynamic memory for that pointer or array.
// 9. Read in the information from the file of that amount into the pointer - array.
// 10. Verify you have all the information.
// 11. Close the file handle.
// 12. Process some more information on the actual pointer or array itself
// based on its orientation, its bits per pixel, its dimensions, the color type, the compression type, and or if it exists encryption type.
// 13. Store the modified data from the array into Your Structure (Texture - Class/Struct).
// 14. Free up dynamic memory...
// 15. typically return the texture through the parameter list as a reference
// 16. typically return an unsigned int as the Texture's numerical ID.
}
This is quite effective; efficient, doesn't need any use of smart pointers; is fast especially if inlining the function. This type of function can either be a stand alone or even a member of a class. If a pattern follows this then it is quite safe to use new & delete or new[] & delete[] if done properly.
这是很有效的;高效,不需要任何智能指针;是快速的,特别是如果内联功能。这种类型的函数可以是独立的,也可以是类的成员。如果一个模式遵循这一点,那么如果操作得当,使用new & delete或new[] & delete[]是相当安全的。
EDIT
编辑
In the mentioned case(s) above sometimes you want the raw pointers and you want it on the heap. Let's say you have an application that will load say 5,000 texture files, 500 model files, 20 scene files, 500-1000 audio files. You do not want your loading time to be slow, you also want it to be "cache" friendly. Texture loading is very good example of having the pointer on the heap as opposed to the functions stack because the texture could be large in size exceeding your local memory capabilities.
在上面提到的情况中,有时您需要原始指针并希望它位于堆中。假设你有一个应用程序,它会加载5000个纹理文件,500个模型文件,20个场景文件,500-1000个音频文件。您不希望加载时间变慢,您还希望它对“缓存”友好。纹理加载是堆上指针而不是函数堆栈的一个很好的例子,因为纹理的大小可能超过您的本地内存能力。
In this context you will be calling these load functions once per object, but you will be calling them several times. After you loaded & created your resources or assets and stored them internally is when and where you would want to use containers instead of arrays and smart pointers instead of raw pointers.
在这个上下文中,您将为每个对象调用一次这些load函数,但是您将多次调用它们。在您加载和创建资源或资产并在内部存储它们之后,您将希望使用容器而不是数组和智能指针,而不是原始指针。
You will load a single asset once, but you may have 100s or 1000s of instances of it. It is with these instances that you would prefer the use of containers and the use of smart pointers to manage their memory within your application over raw pointers and arrays. The initial loading is where you would prefer to be closer to the metal without all the extra unwanted overhead.
您将加载单个资产一次,但是您可能有100个或1000个这样的实例。通过这些实例,您更希望使用容器和智能指针来管理应用程序中的内存,而不是原始指针和数组。最初的负载是你希望更接近金属而不需要额外的开销。
If you were working on a A+ class game and you could save your audience 15 to 30s or more of loading time per loading screen then you are in the winners circle. Yes care does need to be taken and yes you can still have unhandled exceptions, but no code is 100% full proof.
如果你正在做一个a +类游戏,你可以为你的观众节省15到30秒甚至更多的加载时间,那么你就进入了赢家圈。是的,确实需要小心,是的,您仍然可以有未处理的异常,但是没有代码是100%完全证明的。
This type of design is rarely prone to memory leaks except for those exceptions which can still be handled in many of the cases. Also to safely manage raw pointers, preprocessor macros work well for easy clean up.
这种类型的设计很少容易出现内存泄漏,除了那些在许多情况下仍然可以处理的异常。此外,为了安全地管理原始指针,预处理器宏可以很好地进行清理。
Many of these library types also work and deal with raw data
, raw memory allocation
, etc. and many times smart pointers don't necessarily fit these types of jobs.
许多此类库类型还可以处理原始数据、原始内存分配等,而且很多时候,智能指针并不一定适合这些类型的作业。
#19
-2
When you want to create multidimensional arrays but aren't familiar with C++11 syntax like std::move, or aren't familiar with writing custom deleters for smart pointers.
如果您希望创建多维数组,但不熟悉std::move等c++ 11语法,或者不熟悉为智能指针编写自定义删除程序。