malloc 和新与新的展示位置

发布于 2024-12-28 16:11:10 字数 1134 浏览 1 评论 0 原文

过去几天我一直在研究这个问题,到目前为止,除了教条论点或诉诸传统(即“这是 C++ 方式!”)之外,我还没有真正找到任何令人信服的东西。

如果我正在创建一个对象数组,那么使用以下命令的令人信服的原因(除了易用性之外)是什么:

#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=new my_object [MY_ARRAY_SIZE];

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i]=my_object(i);

over

#define MEMORY_ERROR -1
#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);
if (my_object==NULL) throw MEMORY_ERROR;

for (int i=0;i<MY_ARRAY_SIZE;++i) new (my_array+i) my_object (i);

据我所知,后者比前者更有效(因为您没有将内存初始化为某些非-随机值/不必要地调用默认构造函数),唯一的区别实际上是你清理的一个:

delete [] my_array;

和另一个你清理的:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

free(my_array);

我出于一个令人信服的原因而退出。据我所知,它是 C++(而不是 C),因此不应该使用 mallocfree 的事实并非如此 - 令人信服的和它教条一样多。我是否遗漏了一些使 new [] 优于 malloc 的东西?

我的意思是,据我所知,您甚至根本不能使用 new [] 来创建一个没有默认、无参数构造函数的数组,而因此可以使用malloc方法。

I've been looking into this for the past few days, and so far I haven't really found anything convincing other than dogmatic arguments or appeals to tradition (i.e. "it's the C++ way!").

If I'm creating an array of objects, what is the compelling reason (other than ease) for using:

#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=new my_object [MY_ARRAY_SIZE];

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i]=my_object(i);

over

#define MEMORY_ERROR -1
#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);
if (my_object==NULL) throw MEMORY_ERROR;

for (int i=0;i<MY_ARRAY_SIZE;++i) new (my_array+i) my_object (i);

As far as I can tell the latter is much more efficient than the former (since you don't initialize memory to some non-random value/call default constructors unnecessarily), and the only difference really is the fact that one you clean up with:

delete [] my_array;

and the other you clean up with:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

free(my_array);

I'm out for a compelling reason. Appeals to the fact that it's C++ (not C) and therefore malloc and free shouldn't be used isn't -- as far as I can tell -- compelling as much as it is dogmatic. Is there something I'm missing that makes new [] superior to malloc?

I mean, as best I can tell, you can't even use new [] -- at all -- to make an array of things that don't have a default, parameterless constructor, whereas the malloc method can thusly be used.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

魂ガ小子 2025-01-04 16:11:10

我出于令人信服的原因而退出。

这取决于你如何定义“引人注目”。迄今为止您所拒绝的许多论点对于大多数 C++ 程序员来说肯定是有吸引力的,因为您的建议不是在 C++ 中分配裸数组的标准方法。

简单的事实是:是的,您绝对可以按照您描述的方式做事。您所描述的内容没有理由不起作用。

但话又说回来,你可以在 C 中使用虚函数。如果你投入时间和精力,你可以用纯 C 实现类和继承。这些也都是完全实用的。

因此,重要的不是某件事能否发挥作用。但更多的是关于成本是什么。在 C 中实现继承和虚函数比在 C++ 中更容易出错。在 C 中实现它的方法有多种,这会导致实现不兼容。然而,因为它们是 C++ 的一流语言功能,所以有人不太可能手动实现该语言提供的功能。这样,大家的继承和虚函数就可以配合C++的规则了。

这也是同样的道理。那么手动 malloc/free 数组管理的收益和损失是什么?

我不能说我接下来要说的任何内容对你来说都是“令人信服的理由”。我相当怀疑它会,因为你似乎已经下定决心了。但郑重声明:

性能

您声明如下:

据我所知,后者比前者更有效(因为您不会将内存初始化为某些非随机值/不必要地调用默认构造函数),唯一的区别实际上是您清理:

这种说法表明效率增益主要在于所讨论对象的构造中。即调用了哪些构造函数。该语句假定您不想想要调用默认构造函数;您仅使用默认构造函数来创建数组,然后使用真正的初始化函数将实际数据放入对象中。

好吧...如果这不是您想要做的怎么办?如果您想要创建一个默认构造的数组怎么办?在这种情况下,这种优势就完全消失了。

脆弱性

让我们假设数组中的每个对象都需要有一个专门的构造函数或对其进行调用的东西,这样初始化数组就需要这种东西。但请考虑您的销毁代码:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

对于简单的情况,这很好。您有一个宏或 const 变量来表示您有多少个对象。然后循环遍历每个元素以销毁数据。这对于一个简单的例子来说非常好。

现在考虑一个真实的应用程序,而不是一个示例。您将在多少个不同的地方创建数组?几十个?数百个?每一个都需要有自己的 for 循环来初始化数组。每一个都需要有自己的 for 循环来销毁数组。

即使输入错误一次,您也可能会损坏内存。或者不删除某些东西。或者任何其他可怕的事情。

这里有一个重要的问题:对于给定的数组,你在哪里保存大小?您知道为创建的每个数组分配了多少项吗?每个数组可能都有自己的方式来知道它存储了多少项。因此每个析构函数循环都需要正确获取这些数据。如果弄错了……繁荣。

然后我们有异常安全,这是一个全新的蠕虫罐头。如果其中一个构造函数抛出异常,则需要销毁先前构造的对象。你的代码没有这样做;它不是异常安全的。

现在,考虑替代方案:

delete[] my_array;

这不会失败。它总是会破坏每一个元素。它跟踪数组的大小,并且是异常安全的。所以它保证能够工作。它不可能工作(只要你用new[]分配它)。

当然,你可以说你可以将数组包装在一个对象中。这是有道理的。您甚至可以在数组的类型元素上模板化对象。这样,所有析构器代码都是相同的。大小包含在对象中。也许,只是也许,您意识到用户应该对内存分配的特定方式有一定的控制,这样它就不仅仅是malloc/free

恭喜:您刚刚重新发明了 std::vector

这就是为什么许多 C++ 程序员甚至不再输入 new[] 的原因。

灵活性

您的代码使用malloc/free。但假设我正在做一些分析。我意识到对于某些频繁创建的类型来说,malloc/free 的成本太高了。我为他们创建了一个特殊的内存管理器。但是如何将所有数组分配挂钩到它们呢?

好吧,我必须在代码库中搜索创建/销毁这些类型的数组的任何位置。然后我必须相应地改变他们的内存分配器。然后我必须不断监视代码库,以便其他人不会更改这些分配器或引入使用不同分配器的新数组代码。

如果我使用 new[]/delete[],我可以使用运算符重载。我只是为这些类型的运算符 new[]delete[] 提供重载。无需更改任何代码。对于某人来说,规避这些超载要困难得多;他们必须积极尝试。等等。

因此,我获得了更大的灵活性和合理的保证,我的分配器将在应该使用的地方使用。

可读性

考虑一下:

my_object *my_array = new my_object[10];
for (int i=0; i<MY_ARRAY_SIZE; ++i)
  my_array[i]=my_object(i);

//... Do stuff with the array

delete [] my_array;

与此比较:

my_object *my_array = (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE);
if(my_object==NULL)
  throw MEMORY_ERROR;

int i;
try
{
    for(i=0; i<MY_ARRAY_SIZE; ++i)
      new(my_array+i) my_object(i);
}
catch(...)  //Exception safety.
{
    for(i; i>0; --i)  //The i-th object was not successfully constructed
        my_array[i-1].~T();
    throw;
}

//... Do stuff with the array

for(int i=MY_ARRAY_SIZE; i>=0; --i)
  my_array[i].~T();
free(my_array);

客观地说,哪一个更容易阅读和理解正在发生的事情?

只需查看以下语句:(my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE)。这是一个非常低级的事情。你没有分配任何东西的数组;你正在分配一大块内存。您必须手动计算内存块的大小,以匹配对象的大小*您想要的对象的数量。它甚至还有演员阵容。

相比之下,new my_object[10] 讲述了这个故事。 new 是“创建类型实例”的 C++ 关键字。 my_object[10]my_object 类型的 10 个元素数组。它简单、明显且直观。没有转换,没有字节大小的计算,什么都没有。

malloc 方法需要学习如何惯用地使用mallocnew 方法只需要了解 new 的工作原理即可。发生的事情变得不那么冗长并且更加明显。

此外,在 malloc 语句之后,您实际上并没有对象数组。 malloc 只是返回一个内存块,您已告诉 C++ 编译器假装该内存块是指向对象的指针(带有强制转换)。它不是一个对象数组,因为C++中的对象有生命周期。对象的生命周期直到其被构造之后才开始。该内存中还没有调用过构造函数,因此其中没有活动对象。

此时的 my_array 不是一个数组;它只是一个内存块。在您在下一步中构造它们之前,它不会成为 my_object 的数组。对于新程序员来说,这非常不直观;需要经验丰富的 C++ 技术人员(可能是从 C 语言学习的人)才能知道这些不是活动对象,应该小心对待。该指针的行为尚未像正确的 my_object* 那样,因为它尚未指向任何 my_object

相比之下,在 new[] 情况下确实有活动对象。对象已构建;它们是活的并且完全成形。您可以像使用任何其他 my_object* 一样使用此指针。

Fin

以上均未表明此机制在适当的情况下没有潜在用处。但承认某物在某些情况下的效用是一回事。说它应该是默认的做事方式是另一回事。

I'm out for a compelling reason.

It depends on how you define "compelling". Many of the arguments you have thus far rejected are certainly compelling to most C++ programmers, as your suggestion is not the standard way to allocate naked arrays in C++.

The simple fact is this: yes, you absolutely can do things the way you describe. There is no reason that what you are describing will not function.

But then again, you can have virtual functions in C. You can implement classes and inheritance in plain C, if you put the time and effort into it. Those are entirely functional as well.

Therefore, what matters is not whether something can work. But more on what the costs are. It's much more error prone to implement inheritance and virtual functions in C than C++. There are multiple ways to implement it in C, which leads to incompatible implementations. Whereas, because they're first-class language features of C++, it's highly unlikely that someone would manually implement what the language offers. Thus, everyone's inheritance and virtual functions can cooperate with the rules of C++.

The same goes for this. So what are the gains and the losses from manual malloc/free array management?

I can't say that any of what I'm about to say constitutes a "compelling reason" for you. I rather doubt it will, since you seem to have made up your mind. But for the record:

Performance

You claim the following:

As far as I can tell the latter is much more efficient than the former (since you don't initialize memory to some non-random value/call default constructors unnecessarily), and the only difference really is the fact that one you clean up with:

This statement suggests that the efficiency gain is primarily in the construction of the objects in question. That is, which constructors are called. The statement presupposes that you don't want to call the default constructor; that you use a default constructor just to create the array, then use the real initialization function to put the actual data into the object.

Well... what if that's not what you want to do? What if what you want to do is create an empty array, one that is default constructed? In this case, this advantage disappears entirely.

Fragility

Let's assume that each object in the array needs to have a specialized constructor or something called on it, such that initializing the array requires this sort of thing. But consider your destruction code:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

For a simple case, this is fine. You have a macro or const variable that says how many objects you have. And you loop over each element to destroy the data. That's great for a simple example.

Now consider a real application, not an example. How many different places will you be creating an array in? Dozens? Hundreds? Each and every one will need to have its own for loop for initializing the array. Each and every one will need to have its own for loop for destroying the array.

Mis-type this even once, and you can corrupt memory. Or not delete something. Or any number of other horrible things.

And here's an important question: for a given array, where do you keep the size? Do you know how many items you allocated for every array that you create? Each array will probably have its own way of knowing how many items it stores. So each destructor loop will need to fetch this data properly. If it gets it wrong... boom.

And then we have exception safety, which is a whole new can of worms. If one of the constructors throws an exception, the previously constructed objects need to be destructed. Your code doesn't do that; it's not exception-safe.

Now, consider the alternative:

delete[] my_array;

This can't fail. It will always destroy every element. It tracks the size of the array, and it's exception-safe. So it is guaranteed to work. It can't not work (as long as you allocated it with new[]).

Of course, you could say that you could wrap the array in an object. That makes sense. You might even template the object on the type elements of the array. That way, all the desturctor code is the same. The size is contained in the object. And maybe, just maybe, you realize that the user should have some control over the particular way the memory is allocated, so that it's not just malloc/free.

Congratulations: you just re-invented std::vector.

Which is why many C++ programmers don't even type new[] anymore.

Flexibility

Your code uses malloc/free. But let's say I'm doing some profiling. And I realize that malloc/free for certain frequently created types is just too expensive. I create a special memory manager for them. But how to hook all of the array allocations to them?

Well, I have to search the codebase for any location where you create/destroy arrays of these types. And then I have to change their memory allocators accordingly. And then I have to continuously watch the codebase so that someone else doesn't change those allocators back or introduce new array code that uses different allocators.

If I were instead using new[]/delete[], I could use operator overloading. I simply provide an overload for operators new[] and delete[] for those types. No code has to change. It's much more difficult for someone to circumvent these overloads; they have to actively try to. And so forth.

So I get greater flexibility and reasonable assurance that my allocators will be used where they should be used.

Readability

Consider this:

my_object *my_array = new my_object[10];
for (int i=0; i<MY_ARRAY_SIZE; ++i)
  my_array[i]=my_object(i);

//... Do stuff with the array

delete [] my_array;

Compare it to this:

my_object *my_array = (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE);
if(my_object==NULL)
  throw MEMORY_ERROR;

int i;
try
{
    for(i=0; i<MY_ARRAY_SIZE; ++i)
      new(my_array+i) my_object(i);
}
catch(...)  //Exception safety.
{
    for(i; i>0; --i)  //The i-th object was not successfully constructed
        my_array[i-1].~T();
    throw;
}

//... Do stuff with the array

for(int i=MY_ARRAY_SIZE; i>=0; --i)
  my_array[i].~T();
free(my_array);

Objectively speaking, which one of these is easier to read and understand what's going on?

Just look at this statement: (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE). This is a very low level thing. You're not allocating an array of anything; you're allocating a hunk of memory. You have to manually compute the size of the hunk of memory to match the size of the object * the number of objects you want. It even features a cast.

By contrast, new my_object[10] tells the story. new is the C++ keyword for "create instances of types". my_object[10] is a 10 element array of my_object type. It's simple, obvious, and intuitive. There's no casting, no computing of byte sizes, nothing.

The malloc method requires learning how to use malloc idiomatically. The new method requires just understanding how new works. It's much less verbose and much more obvious what's going on.

Furthermore, after the malloc statement, you do not in fact have an array of objects. malloc simply returns a block of memory that you have told the C++ compiler to pretend is a pointer to an object (with a cast). It isn't an array of objects, because objects in C++ have lifetimes. And an object's lifetime does not begin until it is constructed. Nothing in that memory has had a constructor called on it yet, and therefore there are no living objects in it.

my_array at that point is not an array; it's just a block of memory. It doesn't become an array of my_objects until you construct them in the next step. This is incredibly unintuitive to a new programmer; it takes a seasoned C++ hand (one who probably learned from C) to know that those aren't live objects and should be treated with care. The pointer does not yet behave like a proper my_object*, because it doesn't point to any my_objects yet.

By contrast, you do have living objects in the new[] case. The objects have been constructed; they are live and fully-formed. You can use this pointer just like any other my_object*.

Fin

None of the above says that this mechanism isn't potentially useful in the right circumstances. But it's one thing to acknowledge the utility of something in certain circumstances. It's quite another to say that it should be the default way of doing things.

萤火眠眠 2025-01-04 16:11:10

如果您不想通过隐式构造函数调用来初始化内存,而只需要为 placement new 提供有保证的内存分配,那么使用 malloc 就完全没问题了。 >free 而不是 new[]delete[]

使用 new 而不是 malloc令人信服原因是 new 通过构造函数调用提供隐式初始化,从而节省额外的< code>memset 或相关函数调用会发布 malloc 并且对于 new 您不需要在每次分配后检查 NULL ,只需包含异常处理程序即可完成工作,从而节省您多余的错误检查,这与malloc
这两个令人信服的理由不适用于您的使用。

哪一种性能高效只能通过分析来确定,您现在的方法没有任何问题。顺便说一句,我也没有看到为什么使用 malloc 而不是 new[] 的令人信服的理由。

If you do not want to get your memory initialized by implicit constructor calls, and just need an assured memory allocation for placement new then it is perfectly fine to use malloc and free instead of new[] and delete[].

The compelling reasons of using new over malloc is that new provides implicit initialization through constructor calls, saving you additional memset or related function calls post an malloc And that for new you do not need to check for NULL after every allocation, just enclosing exception handlers will do the job saving you redundant error checking unlike malloc.
These both compelling reasons do not apply to your usage.

which one is performance efficient can only be determined by profiling, there is nothing wrong in the approach you have now. On a side note I don't see a compelling reason as to why use malloc over new[] either.

尘曦 2025-01-04 16:11:10

我不会说。

最好的方法是:

std::vector<my_object>   my_array;
my_array.reserve(MY_ARRAY_SIZE);

for (int i=0;i<MY_ARRAY_SIZE;++i)
{    my_array.push_back(my_object(i));
}

这是因为内部向量可能正在为您进行新的放置。它还管理您没有考虑到的与内存管理相关的所有其他问题。

I would say neither.

The best way to do it would be:

std::vector<my_object>   my_array;
my_array.reserve(MY_ARRAY_SIZE);

for (int i=0;i<MY_ARRAY_SIZE;++i)
{    my_array.push_back(my_object(i));
}

This is because internally vector is probably doing the placement new for you. It also managing all the other problems associated with memory management that you are not taking into account.

若言繁花未落 2025-01-04 16:11:10

您在这里重新实现了 new[]/delete[],并且您所编写的内容在开发专用分配器中非常常见。

与分配相比,调用简单构造函数的开销将花费很少的时间。它不一定“更高效”——它取决于默认构造函数和 operator= 的复杂性。

尚未提及的一件好事是,new[]/delete[] 可以知道数组的大小。 delete[] 只是执行正确的操作并在被要求时销毁所有元素。拖动一个附加变量(或三个)以便您准确地了解如何销毁数组是一件痛苦的事情。然而,专用的集合类型将是一个不错的选择。

为了方便起见,最好使用 new[]/delete[] 。它们带来的开销很小,并且可以使您避免许多愚蠢的错误。您是否有足够的理由取消此功能并在各处使用集合/容器来支持您的自定义构造?我已经实现了这个分配器——真正的混乱是为实践中所需的所有构造变体创建函子。无论如何,您通常会以牺牲程序为代价获得更精确的执行,而程序通常比每个人都知道的习惯用法更难维护。

You've reimplemented new[]/delete[] here, and what you have written is pretty common in developing specialized allocators.

The overhead of calling simple constructors will take little time compared the allocation. It's not necessarily 'much more efficient' -- it depends on the complexity of the default constructor, and of operator=.

One nice thing that has not been mentioned yet is that the array's size is known by new[]/delete[]. delete[] just does the right and destructs all elements when asked. Dragging an additional variable (or three) around so you exactly how to destroy the array is a pain. A dedicated collection type would be a fine alternative, however.

new[]/delete[] are preferable for convenience. They introduce little overhead, and could save you from a lot of silly errors. Are you compelled enough to take away this functionality and use a collection/container everywhere to support your custom construction? I've implemented this allocator -- the real mess is creating functors for all the construction variations you need in practice. At any rate, you often have a more exact execution at the expense of a program which is often more difficult to maintain than the idioms everybody knows.

熊抱啵儿 2025-01-04 16:11:10

恕我直言,两者都很难看,最好使用向量。只要确保提前分配空间以提高性能即可。

或者:

std::vector<my_object> my_array(MY_ARRAY_SIZE);

如果您想使用所有条目的默认值进行初始化。

my_object basic;
std::vector<my_object> my_array(MY_ARRAY_SIZE, basic);

或者,如果您不想构造对象但确实想保留空间:

std::vector<my_object> my_array;
my_array.reserve(MY_ARRAY_SIZE);

那么如果您需要将其作为 C 样式指针数组访问(只需确保在保留旧指针的同时不添加内容,但无论如何,你不能用常规的c风格数组来做到这一点。)

my_object* carray = &my_array[0];      
my_object* carray = &my_array.front(); // Or the C++ way

访问单个元素:

my_object value = my_array[i];    // The non-safe c-like faster way
my_object value = my_array.at(i); // With bounds checking, throws range exception

Typedef for beautiful:

typedef std::vector<my_object> object_vect;

通过引用将它们传递给函数:

void some_function(const object_vect& my_array);

编辑:
在 C++11 中还有 std::array。但它的问题是它的大小是通过模板完成的,因此您不能在运行时制作不同大小的大小,并且不能将其传递到函数中,除非它们期望完全相同的大小(或者模板函数本身)。但它对于缓冲区之类的东西很有用。

std::array<int, 1024> my_array;

编辑2:
另外,在 C++11 中,有一个新的 emplace_back 作为 push_back 的替代品。这基本上允许您“移动”您的对象(或直接在向量中构造您的对象)并为您保存一个副本。

std::vector<SomeClass> v;
SomeClass bob {"Bob", "Ross", 10.34f};
v.emplace_back(bob);
v.emplace_back("Another", "One", 111.0f); // <- Note this doesn't work with initialization lists ☹

IMHO there both ugly, it's better to use vectors. Just make sure to allocate the space in advance for performance.

Either:

std::vector<my_object> my_array(MY_ARRAY_SIZE);

If you want to initialize with a default value for all entries.

my_object basic;
std::vector<my_object> my_array(MY_ARRAY_SIZE, basic);

Or if you don't want to construct the objects but do want to reserve the space:

std::vector<my_object> my_array;
my_array.reserve(MY_ARRAY_SIZE);

Then if you need to access it as a C-Style pointer array just (just make sure you don't add stuff while keeping the old pointer but you couldn't do that with regular c-style arrays anyway.)

my_object* carray = &my_array[0];      
my_object* carray = &my_array.front(); // Or the C++ way

Access individual elements:

my_object value = my_array[i];    // The non-safe c-like faster way
my_object value = my_array.at(i); // With bounds checking, throws range exception

Typedef for pretty:

typedef std::vector<my_object> object_vect;

Pass them around functions with references:

void some_function(const object_vect& my_array);

EDIT:
IN C++11 there is also std::array. The problem with it though is it's size is done via a template so you can't make different sized ones at runtime and you cant pass it into functions unless they are expecting that exact same size (or are template functions themselves). But it can be useful for things like buffers.

std::array<int, 1024> my_array;

EDIT2:
Also in C++11 there is a new emplace_back as an alternative to push_back. This basically allows you to 'move' your object (or construct your object directly in the vector) and saves you a copy.

std::vector<SomeClass> v;
SomeClass bob {"Bob", "Ross", 10.34f};
v.emplace_back(bob);
v.emplace_back("Another", "One", 111.0f); // <- Note this doesn't work with initialization lists ☹
生死何惧 2025-01-04 16:11:10

哦,好吧,我在想,鉴于答案的数量,没有理由介入……但我想我和其他人一样被吸引了。让我们来看看

  1. 为什么你的解决方案被破坏了
  2. C++11 处理原始内存的新设施
  3. 更简单的方法来完成这个
  4. 任务 建议

1。为什么您的解决方案被破坏了

首先,您提供的两个片段并不相同。 new[] 正常工作,而您的在出现异常时会严重失败。

new[] 的作用是跟踪所构造的对象的数量,因此,如果在调用第三个构造函数期间发生异常,它会正确调用已经存在的第二个对象的析构函数构造的对象。

然而,您的解决方案严重失败:

  • 要么您根本不处理异常(并且泄漏严重)
  • ,要么您只是尝试调用整个数组的析构函数,即使它是半构建的(可能会崩溃,但谁知道未定义的行为)

所以两者显然不相等。 你的坏了

2。 C++11 用于处理原始内存的新设施

在 C++11 中,委员会成员已经意识到我们是多么喜欢摆弄原始内存,并且他们引入了一些设施来帮助我们更高效、更安全地这样做。

检查 cppreference 的 简介。此示例展示了新功能 (*):

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>

int main()
{
    const std::string s[] = {"This", "is", "a", "test", "."};
    std::string* p = std::get_temporary_buffer<std::string>(5).first;

    std::copy(std::begin(s), std::end(s),
              std::raw_storage_iterator<std::string*, std::string>(p));

    for(std::string* i = p; i!=p+5; ++i) {
        std::cout << *i << '\n';
        i->~basic_string<char>();
    }
    std::return_temporary_buffer(p);
}

请注意,get_temporary_buffer 不会抛出异常,它返回实际已分配内存作为 pair< 的第二个成员的元素数量。 /code> (因此 .first 来获取指针)。

(*) 或者也许并不像 MooingDuck 所说的那么新。

3。完成此操作的更简单方法

据我所知,您真正需要的是一种类型化内存池,其中某些位置可能已初始化。

你知道boost::可选

它基本上是一个原始内存区域,可以容纳给定类型(模板参数)的一项,但默认情况下没有任何内容。它具有与指针类似的接口,可让您查询内存是否实际被占用。最后,使用就地工厂< /a> 如果有问题,您可以安全地使用它,而无需复制对象。

好吧,您的用例确实看起来像 std::vector<升压::可选 > 对我来说(或者可能是一个双端队列?)

4。建议

最后,如果您真的想自己做,无论是为了学习还是因为没有 STL 容器真正适合您,我建议您将其包装在一个对象中以避免代码遍布各处。

不要忘记:不要重复自己!

使用对象(模板化),您可以在一个位置捕获设计的精髓,然后在任何地方重复使用它。

当然,为什么不在这样做时利用新的 C++11 设施呢:)?

Oh well, I was thinking that given the number of answers there would be no reason to step in... but I guess I am drawn in as the others. Let's go

  1. Why your solution is broken
  2. C++11 new facilities for handling raw memory
  3. Simpler way to get this done
  4. Advices

1. Why your solution is broken

First, the two snippets you presented are not equivalent. new[] just works, yours fails horribly in the presence of Exceptions.

What new[] does under the cover is that it keeps track of the number of objects that were constructed, so that if an exception occurs during say the 3rd constructor call it properly calls the destructor for the 2 already constructed objects.

Your solution however fails horribly:

  • either you don't handle exceptions at all (and leak horribly)
  • or you just try to call the destructors on the whole array even though it's half built (likely crashing, but who knows with undefined behavior)

So the two are clearly not equivalent. Yours is broken

2. C++11 new facilities for handling raw memory

In C++11, the comittee members have realized how much we liked fiddling with raw memory and they have introduced facilities to help us doing so more efficiently, and more safely.

Check cppreference's <memory> brief. This example shows off the new goodies (*):

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>

int main()
{
    const std::string s[] = {"This", "is", "a", "test", "."};
    std::string* p = std::get_temporary_buffer<std::string>(5).first;

    std::copy(std::begin(s), std::end(s),
              std::raw_storage_iterator<std::string*, std::string>(p));

    for(std::string* i = p; i!=p+5; ++i) {
        std::cout << *i << '\n';
        i->~basic_string<char>();
    }
    std::return_temporary_buffer(p);
}

Note that get_temporary_buffer is no-throw, it returns the number of elements for which memory has actually been allocated as a second member of the pair (thus the .first to get the pointer).

(*) Or perhaps not so new as MooingDuck remarked.

3. Simpler way to get this done

As far as I am concered, what you really seem to be asking for is a kind of typed memory pool, where some emplacements could not have been initialized.

Do you know about boost::optional ?

It is basically an area of raw memory that can fit one item of a given type (template parameter) but defaults with having nothing in instead. It has a similar interface to a pointer and let you query whether or not the memory is actually occupied. Finally, using the In-Place Factories you can safely use it without copying objects if it is a concern.

Well, your use case really looks like a std::vector< boost::optional<T> > to me (or perhaps a deque?)

4. Advices

Finally, in case you really want to do it on your own, whether for learning or because no STL container really suits you, I do suggest you wrap this up in an object to avoid the code sprawling all over the place.

Don't forget: Don't Repeat Yourself!

With an object (templated) you can capture the essence of your design in one single place, and then reuse it everywhere.

And of course, why not take advantage of the new C++11 facilities while doing so :) ?

戏蝶舞 2025-01-04 16:11:10

您应该使用向量

You should use vectors.

神经大条 2025-01-04 16:11:10

不管是否教条,这正是所有 STL 容器进行分配和初始化的操作。

他们使用分配器,然后分配未初始化的空间并通过容器构造函数对其进行初始化。

如果这(就像很多人常说的)“不是c++”,标准库怎么能这样实现呢?

如果您只是不想使用 malloc / free,则可以仅使用 new char[] 分配“字节”。

myobjet* pvext = reinterpret_cast<myobject*>(new char[sizeof(myobject)*vectsize]);
for(int i=0; i<vectsize; ++i) new(myobject+i)myobject(params);
...
for(int i=vectsize-1; i!=0u-1; --i) (myobject+i)->~myobject();
delete[] reinterpret_cast<char*>(myobject);

这使您可以利用初始化和分配之间的分离,同时仍然利用分配异常机制。

请注意,将第一行和最后一行放入 myallocator 类中,将第二行和倒数第二行放入 myvector 类中,我们有...刚刚重新实现了 std::vector; >

Dogmatic or not, that is exactly what ALL the STL container do to allocate and initialize.

They use an allocator then allocates uninitialized space and initialize it by means of the container constructors.

If this (like many people use to say) "is not c++" how can be the standard library just be implemented like that?

If you just don't want to use malloc / free, you can allocate "bytes" with just new char[]

myobjet* pvext = reinterpret_cast<myobject*>(new char[sizeof(myobject)*vectsize]);
for(int i=0; i<vectsize; ++i) new(myobject+i)myobject(params);
...
for(int i=vectsize-1; i!=0u-1; --i) (myobject+i)->~myobject();
delete[] reinterpret_cast<char*>(myobject);

This lets you take advantage of the separation between initialization and allocation, still taking adwantage of the new allocation exception mechanism.

Note that, putting my first and last line into an myallocator<myobject> class and the second ands second-last into a myvector<myobject> class, we have ... just reimplemented std::vector<myobject, std::allocator<myobject> >

一束光,穿透我孤独的魂 2025-01-04 16:11:10

您在这里展示的实际上是使用与系统通用分配器不同的内存分配器时的方法 - 在这种情况下,您将使用分配器 (alloc->malloc(sizeof(my_object))) 分配内存,然后使用放置 new 运算符来初始化它。这在高效的内存管理方面有很多优点,并且在标准模板库中很常见。

What you have shown here is actually the way to go when using a memory allocator different than the system general allocator - in that case you would allocate your memory using the allocator (alloc->malloc(sizeof(my_object))) and then use the placement new operator to initialize it. This has many advantages in efficient memory management and quite common in the standard template library.

逆流 2025-01-04 16:11:10

如果您正在编写一个模仿 std::vector 功能的类或需要控制内存分配/对象创建(在数组中插入/删除等) - 这就是要走的路。在这种情况下,这不是“不调用默认构造函数”的问题。这就变成了一个能够“分配原始内存、memmove旧对象,然后在旧对象的地址创建新对象”的问题,以及能够使用某种形式的realloc<的问题。 /代码>等等。毫无疑问,自定义分配+放置 new 更加灵活......我知道,我有点醉了,但是 std::vector 适合胆小鬼......效率 - 人们可以编写自己的 std::vector 版本,该版本至少与最常用的一样快(并且很可能更小,就 sizeof() 而言) 80%的std::vector 功能可能需要不到 3 小时。

If you are writing a class that mimics functionality of std::vector or needs control over memory allocation/object creation (insertion in array / deletion etc.) - that's the way to go. In this case, it's not a question of "not calling default constructor". It becomes a question of being able to "allocate raw memory, memmove old objects there and then create new objects at the olds' addresses", question of being able to use some form of realloc and so on. Unquestionably, custom allocation + placement new are way more flexible... I know, I'm a bit drunk, but std::vector is for sissies... About efficiency - one can write their own version of std::vector that will be AT LEAST as fast ( and most likely smaller, in terms of sizeof() ) with most used 80% of std::vector functionality in, probably, less than 3 hours.

稚气少女 2025-01-04 16:11:10
my_object * my_array=new my_object [10];

这将是一个包含对象的数组。

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);

这将是一个与对象大小相同的数组,但它们可能会“损坏”。例如,如果您的类有虚拟函数,那么您将无法调用它们。请注意,不仅仅是您的成员数据可能不一致,而且整个对象实际上是“损坏的”(缺乏更好的词)

我并不是说执行第二个是错误的,只要您知道这一点。

my_object * my_array=new my_object [10];

This will be an array with objects.

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);

This will be an array the size of your objects, but they may be "broken". If your class has virtual funcitons for instance, then you won't be able to call those. Note that it's not just your member data that may be inconsistent, but the entire object is actully "broken" (in lack of a better word)

I'm not saying it's wrong to do the second one, just as long as you know this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文