指向数组的`new`指针的首选方法

时间:2023-01-29 09:57:04

Why is this an error:

为什么这是一个错误:

typedef int H[4];

H * h = new H;        // error: cannot convert 'int*' to 'int (*)[4]' in initialization

?

Furthermore, why is this not an error:

此外,为什么这不是一个错误:

H * h = new H[1];

?

Why does the compiler consider that new H returns an int *, whereas new H[1] returns an H * as expected ?

为什么编译器认为新的H返回int *,而新的H [1]按预期返回H *?

To put it another way: why is it that T * t = new T; is correct for a general type T, but is not correct when T is an array type ?

换句话说:为什么T * t = new T;对于一般类型T是正确的,但是当T是数组类型时是不正确的?

What would be the canonical way to allocate a simple array type such as this via new ?

通过new分配简单数组类型的规范方法是什么?

Note that this is a simplified example, so e.g. new int[4] is not an acceptable workaround - I need to use the actual type from the preceding typedef.

请注意,这是一个简化的示例,例如, new int [4]不是一个可接受的解决方法 - 我需要使用前面的typedef中的实际类型。

Note also that I'm aware that using std::vector, std::array, et al is generally preferable over C-style arrays, but I have a "real world" use case where I need to work with types such as the above.

还要注意,我知道使用std :: vector,std :: array等等通常优于C风格的数组,但我有一个“真实世界”用例,我需要使用类型,如以上。

4 个解决方案

#1


15  

The C++ rule for the return type and value of new T is:

返回类型和新T值的C ++规则是:

  • If T is not an array type, the return type is T *, and the returned value is a pointer to the dynamically allocated object of type T.
  • 如果T不是数组类型,则返回类型为T *,返回值是指向类型为T的动态分配对象的指针。

  • If T is an array of type U, the return type is U *, and the returned value is a pointer to the first element (whose type is U) of the dynamically allocated array of type T.
  • 如果T是类型U的数组,则返回类型为U *,返回值是指向类型为T的动态分配数组的第一个元素(其类型为U)的指针。

Therefore, since your H is an array of int, the return type of new H is int *, not H *.

因此,由于您的H是int数组,因此新H的返回类型是int *,而不是H *。

By the same rule, new H[1] returns H *, but note that you have technicaly allocated a two-dimensional array of ints, sized 1 x 4.

根据相同的规则,新的H [1]返回H *,但请注意,您已经技术分配了一个二维的整数数组,大小为1 x 4。

The best way to get get around this in generic code is indeed to use auto:

在通用代码中解决这个问题的最佳方法是使用auto:

auto h = new H;

Or, if you prefer to highlight the pointer fact:

或者,如果您希望突出显示指针事实:

auto *h = new H;

As for the rationale of this seeming inconsistency in the rules: pointers to arrays are quite "dangerous" in C++ since they behave rather unexpectedly (i.e. you have to be very careful with them not to produce unwanted effects). Let's look at this code:

至于规则中这种看似不一致的基本原理:指向数组的指针在C ++中非常“危险”,因为它们的表现非常出乎意料(即你必须非常小心它们才能产生不必要的效果)。我们来看看这段代码:

typedef int H[4];
H *h = obtain_pointer_to_H_somehow();
h[2] = h[1] + 6;

At first (and maybe even second) glance, the code above seems to add 6 to the second int in the array and store it in the third int. But that's not what it does.

在第一眼(甚至可能是第二眼)看来,上面的代码似乎在数组中的第二个int中加6,并将其存储在第三个int中。但这不是它的作用。

Just like for int *p, p[1] is an int (at the address sizeof(int) bytes offset from p), so for H *h, h[1] is a H, at the address 4 * sizeof(int) bytes offset from h. So the code is interpreted as: take the address in h, add 4 * sizeof(int) bytes to it, then add 6, and then store the resulting address at offset 8 * sizeof(int) from h. Of course, that will fail, since h[2] decays to an rvalue.

就像int * p一样,p [1]是一个int(地址sizeof(int)字节偏离p),所以对于H * h,h [1]是H,地址是4 * sizeof(int )字节偏离h。所以代码被解释为:取h中的地址,向其中添加4 * sizeof(int)字节,然后添加6,然后将结果地址存储在h的偏移量8 * sizeof(int)中。当然,这会失败,因为h [2]衰减到右值。

OK then, you fix it like this:

好的,你修复它像这样:

*h[2] = *h[1] + 6;

Even worse now. [] binds tighter than *, so this will reach into the 5th int object after h (note there are only 4 of them there!), add 6, and write that into the 9th int after h. Writing into random memory FTW.

现在更糟糕了。 []绑定比*更紧,所以这将在h之后到达第5个int对象(注意那里只有4个!),添加6,并在h之后将其写入第9个int。写入随机存储器FTW。

To actually do what the code was probably intended to, it would have to be spelled like this:

要实际执行代码可能的目的,它必须拼写如下:

(*h)[2] = (*h)[1] + 6;

In light of the above, and since what you usually do with a dynamically allocated array is access its elements, it makes more sense for new T[] to return T *.

鉴于上述情况,并且由于您通常使用动态分配的数组来访问其元素,因此新T []返回T *更有意义。

#2


4  

Main Question

[What is the] Preferred method to new pointer to array

[什么是]指向数组的新指针的首选方法

Constraint:

Note also that I'm aware that using std::vector, std::array, et al is generally preferable over C-style arrays, but I have a "real world" use case where I need to work with types such as the above.

还要注意,我知道使用std :: vector,std :: array等等通常优于C风格的数组,但我有一个“真实世界”用例,我需要使用类型,如以上。

Answer:

for the non-array case:

对于非阵列情况:

#include <memory>

auto h = std::make_unique<H>();

// h is now a std::unique_ptr<H> which behaves to all intents and purposes
// like an H* but will safely release resources when it goes out of
// scope
// it is pointing to a default-constructed H

// access the underlying object like this:
h.get(); // yields H*
*h;      // yields H&

For the array case:

对于数组案例:

#include <memory>

auto h = std::make_unique<H[]>(4);

// h is now a std::unique_ptr<H[]> which behaves to all intents and purposes
// like an H* but will safely destruct and deallocate the array 
// when it goes out of scope
// it is pointing to an array of 4 default-constructed Hs

// access the underlying object like this:
h[1];     // yields H& - a reference to the 2nd H
h.get();  //yields H* - as if &h[0]

#3


2  

In the C++ standard new and new[] are specifically separated because they can be be different allocators for performance and efficiency reasons; allocating an array vs. a single object has different usage patterns that allocator implementations optimise for.

在C ++标准中,new和new []是专门分开的,因为出于性能和效率的原因,它们可以是不同的分配器;分配数组与单个对象具有分配器实现优化的不同使用模式。

The syntax is probably different because the compile-time type introspection techniques available when it was standardised wasn't nearly as reliable as it is today.

语法可能不同,因为标准化时可用的编译时类型内省技术并不像现在这样可靠。

To make sensible code, the preferred way IMO would be:

为了制作合理的代码,IMO的首选方式是:

struct H { int values[4]; }

H * h = new H;

That way, your H type logically "contains" your array of four int values - but the in-memory structure should still be compatible (assert(sizeof(H) == 4 * sizeof(int))); and you get to use object-style allocation of your fixed-size arrays. More... C++-ey.

这样,你的H类型在逻辑上“包含”了四个int值的数组 - 但是内存中的结构应该仍然是兼容的(assert(sizeof(H)== 4 * sizeof(int)));并且您可以使用固定大小数组的对象样式分配。更多... C ++ - ey。

#4


1  

You can see why if you basically do the typedef substitution manually:

你可以看到为什么你基本上手动执行typedef替换:

H * h = new H;

Reduces to:

int[4]* h = new int[4];

while

H * h = new H[1];

reduces to:

(int*)[4] h = new int[1][4];

Because of the array to pointer decay rules, this is legal.

由于数组指针衰减规则,这是合法的。

If you want an actual workaround, and you know the type of H, you can do something like:

如果您想要一个实际的解决方法,并且您知道H的类型,您可以执行以下操作:

typedef int* J;
typedef int H[4];

J j = new H; // This will work

#1


15  

The C++ rule for the return type and value of new T is:

返回类型和新T值的C ++规则是:

  • If T is not an array type, the return type is T *, and the returned value is a pointer to the dynamically allocated object of type T.
  • 如果T不是数组类型,则返回类型为T *,返回值是指向类型为T的动态分配对象的指针。

  • If T is an array of type U, the return type is U *, and the returned value is a pointer to the first element (whose type is U) of the dynamically allocated array of type T.
  • 如果T是类型U的数组,则返回类型为U *,返回值是指向类型为T的动态分配数组的第一个元素(其类型为U)的指针。

Therefore, since your H is an array of int, the return type of new H is int *, not H *.

因此,由于您的H是int数组,因此新H的返回类型是int *,而不是H *。

By the same rule, new H[1] returns H *, but note that you have technicaly allocated a two-dimensional array of ints, sized 1 x 4.

根据相同的规则,新的H [1]返回H *,但请注意,您已经技术分配了一个二维的整数数组,大小为1 x 4。

The best way to get get around this in generic code is indeed to use auto:

在通用代码中解决这个问题的最佳方法是使用auto:

auto h = new H;

Or, if you prefer to highlight the pointer fact:

或者,如果您希望突出显示指针事实:

auto *h = new H;

As for the rationale of this seeming inconsistency in the rules: pointers to arrays are quite "dangerous" in C++ since they behave rather unexpectedly (i.e. you have to be very careful with them not to produce unwanted effects). Let's look at this code:

至于规则中这种看似不一致的基本原理:指向数组的指针在C ++中非常“危险”,因为它们的表现非常出乎意料(即你必须非常小心它们才能产生不必要的效果)。我们来看看这段代码:

typedef int H[4];
H *h = obtain_pointer_to_H_somehow();
h[2] = h[1] + 6;

At first (and maybe even second) glance, the code above seems to add 6 to the second int in the array and store it in the third int. But that's not what it does.

在第一眼(甚至可能是第二眼)看来,上面的代码似乎在数组中的第二个int中加6,并将其存储在第三个int中。但这不是它的作用。

Just like for int *p, p[1] is an int (at the address sizeof(int) bytes offset from p), so for H *h, h[1] is a H, at the address 4 * sizeof(int) bytes offset from h. So the code is interpreted as: take the address in h, add 4 * sizeof(int) bytes to it, then add 6, and then store the resulting address at offset 8 * sizeof(int) from h. Of course, that will fail, since h[2] decays to an rvalue.

就像int * p一样,p [1]是一个int(地址sizeof(int)字节偏离p),所以对于H * h,h [1]是H,地址是4 * sizeof(int )字节偏离h。所以代码被解释为:取h中的地址,向其中添加4 * sizeof(int)字节,然后添加6,然后将结果地址存储在h的偏移量8 * sizeof(int)中。当然,这会失败,因为h [2]衰减到右值。

OK then, you fix it like this:

好的,你修复它像这样:

*h[2] = *h[1] + 6;

Even worse now. [] binds tighter than *, so this will reach into the 5th int object after h (note there are only 4 of them there!), add 6, and write that into the 9th int after h. Writing into random memory FTW.

现在更糟糕了。 []绑定比*更紧,所以这将在h之后到达第5个int对象(注意那里只有4个!),添加6,并在h之后将其写入第9个int。写入随机存储器FTW。

To actually do what the code was probably intended to, it would have to be spelled like this:

要实际执行代码可能的目的,它必须拼写如下:

(*h)[2] = (*h)[1] + 6;

In light of the above, and since what you usually do with a dynamically allocated array is access its elements, it makes more sense for new T[] to return T *.

鉴于上述情况,并且由于您通常使用动态分配的数组来访问其元素,因此新T []返回T *更有意义。

#2


4  

Main Question

[What is the] Preferred method to new pointer to array

[什么是]指向数组的新指针的首选方法

Constraint:

Note also that I'm aware that using std::vector, std::array, et al is generally preferable over C-style arrays, but I have a "real world" use case where I need to work with types such as the above.

还要注意,我知道使用std :: vector,std :: array等等通常优于C风格的数组,但我有一个“真实世界”用例,我需要使用类型,如以上。

Answer:

for the non-array case:

对于非阵列情况:

#include <memory>

auto h = std::make_unique<H>();

// h is now a std::unique_ptr<H> which behaves to all intents and purposes
// like an H* but will safely release resources when it goes out of
// scope
// it is pointing to a default-constructed H

// access the underlying object like this:
h.get(); // yields H*
*h;      // yields H&

For the array case:

对于数组案例:

#include <memory>

auto h = std::make_unique<H[]>(4);

// h is now a std::unique_ptr<H[]> which behaves to all intents and purposes
// like an H* but will safely destruct and deallocate the array 
// when it goes out of scope
// it is pointing to an array of 4 default-constructed Hs

// access the underlying object like this:
h[1];     // yields H& - a reference to the 2nd H
h.get();  //yields H* - as if &h[0]

#3


2  

In the C++ standard new and new[] are specifically separated because they can be be different allocators for performance and efficiency reasons; allocating an array vs. a single object has different usage patterns that allocator implementations optimise for.

在C ++标准中,new和new []是专门分开的,因为出于性能和效率的原因,它们可以是不同的分配器;分配数组与单个对象具有分配器实现优化的不同使用模式。

The syntax is probably different because the compile-time type introspection techniques available when it was standardised wasn't nearly as reliable as it is today.

语法可能不同,因为标准化时可用的编译时类型内省技术并不像现在这样可靠。

To make sensible code, the preferred way IMO would be:

为了制作合理的代码,IMO的首选方式是:

struct H { int values[4]; }

H * h = new H;

That way, your H type logically "contains" your array of four int values - but the in-memory structure should still be compatible (assert(sizeof(H) == 4 * sizeof(int))); and you get to use object-style allocation of your fixed-size arrays. More... C++-ey.

这样,你的H类型在逻辑上“包含”了四个int值的数组 - 但是内存中的结构应该仍然是兼容的(assert(sizeof(H)== 4 * sizeof(int)));并且您可以使用固定大小数组的对象样式分配。更多... C ++ - ey。

#4


1  

You can see why if you basically do the typedef substitution manually:

你可以看到为什么你基本上手动执行typedef替换:

H * h = new H;

Reduces to:

int[4]* h = new int[4];

while

H * h = new H[1];

reduces to:

(int*)[4] h = new int[1][4];

Because of the array to pointer decay rules, this is legal.

由于数组指针衰减规则,这是合法的。

If you want an actual workaround, and you know the type of H, you can do something like:

如果您想要一个实际的解决方法,并且您知道H的类型,您可以执行以下操作:

typedef int* J;
typedef int H[4];

J j = new H; // This will work