我们可以使用 std::array 的传统指针算法吗?
Can we use conventional pointer arithmetic with std::array?
我想研究如何在指向 std::array class 的元素的指针上使用旧式指针算法。以下代码(也许不足为奇)无法编译:
int main(int argc, char *argv[])
{
double* data1 = new double[(int)std::pow(2,20)];
std::cout << *data1 << " " << *(data1 +1) << std::endl;
delete data1;
data1 = NULL;
double* data2 = new std::array<double, (int)std::pow(2,20)>;
std::cout << *data2 << " " << *(data2 +1) << std::endl;
delete data2;
data2 = NULL;
return 0;
}
作为练习,我想使用所有传统的指针算法,但不是指向旧式双精度数组,而是希望它指向 std::array 的元素。我对这条线的看法:
double* data2 = new std::array<double, (int)std::pow(2,20)>;
是告诉编译器data2是指向分配的堆的第一个元素的指针std::array<double,(int)std::pow(2,20)>
。
我被教导 double* name = new double[size];
意味着 EXACTLY 以下内容:«堆栈为指向 ONE 的指针分配内存double 并将指针命名为 name
,然后堆分配一个大小为 size
的双精度数组,然后将指针设置为指向数组的第一个元素。»由于上面的代码无法编译,我一定是被教了一些不正确的东西,因为相同的语法不适用于 std::arrays.
这提出了几个问题:
- 语句
type* name = new othertype[size];
的实际含义是什么?
- 如何使用 std::array 实现我想要的?
- 最后,如何使用 std::unqiue_ptr 和 std::make_unique 实现相同的效果?
I have been taught that the double* name = new double[size];
means EXACTLY the following: «Stack allocate memory for a pointer to ONE double and name the pointer name, then heap allocate an array of doubles of size size, then set the pointer to point to the first element of the array.» Since the above code does not compile, I must have been taught something incorrect since the same syntax doesnt work for std::arrays.
您的说法是正确的,但请记住,它的工作方式是 new[]
与 new
是不同的运算符。当您动态分配 std::array
时,您正在调用单个对象 new
,并且返回的指针指向 std::array
对象本身。
您可以对 std::array
的内容进行指针运算。例如,data2.data() + 1
是指向 data2[1]
的指针。请注意,您必须调用 .data()
才能获取指向基础数组的指针。
无论如何,不要动态分配 std::array
对象。尽可能避免动态分配,但如果需要,请使用 std::vector
.
您可以使用 data()
成员函数访问 std::array
的 "raw pointer" 视图。但是,std::array
的要点是您 不必 必须这样做:
int main(int argc, char *argv[])
{
std::array<double, 2> myArray;
double* data = myArray.data();
// Note that the builtin a[b] operator is exactly the same as
// doing *(a+b).
// But you can just use the overloaded operator[] of std::array.
// All of these thus print the same thing:
std::cout << *(data) << " " << *(data+1) << std::endl;
std::cout << data[0] << " " << data[1] << std::endl;
std::cout << myArray[0] << " " << myArray[1] << std::endl;
return 0;
}
广义的意思:
type* name = new othertype[size];
最终成为 "I need a variable name
that's a pointer to type
and initialize that with a contiguous allocation of size
instances of othertype
using new[]
"。请注意,这涉及转换,甚至可能无法正常工作,因为 othertype
和 type
可能不支持该操作。 double
的 std::array
不等同于指向 double
的指针。它是一个指向 std::array
句点的指针,但是如果你想假装它是一个 double
并且你不介意你的程序是否由于 未定义的行为 而崩溃你可以继续。你的编译器应该在这里警告你,如果没有,你的警告不够严格。
标准库容器都是关于迭代器,而不是指针,尤其不是指针运算。迭代器比指针更灵活、更强大,它们可以处理链表、树等奇特的数据结构,而不会给调用者带来太多开销。
一些容器,如 std::vector
和 std::array
支持 "random access iterators",这是一种直接访问其内容的类似指针的形式:a[1]
等等。仔细阅读任何给定容器的文档,因为有些容器允许这样做,而许多则不允许。
请记住 "variable" 和 "allocated on stack" 不一定是同一回事。优化编译器可以并将该指针放在任何它想要的地方,包括寄存器而不是内存,或者如果它认为它可以在不破坏代码表达的行为的情况下摆脱它,那么根本就不会放在任何地方。
如果你想要 std::array
,你真的会这样做,因为标准库容器几乎总是比 C 风格的数组更好:
std::array<double, 2> data2;
如果您需要共享此结构,则需要考虑使用 std::unique_ptr
的费用是否值得。这个东西内存占用很小,复制它也很简单,所以搞一个相对昂贵的内存管理功能是没有意义的。
如果您要传递一个更大的结构,请考虑使用引用并将该结构定位在您拥有的最中心的数据结构中,这样就不需要按设计进行复制。
Can we use conventional pointer arithmetic with std::array
?
是的,当然可以 - 但不能在数组本身上,它是一个对象。相反,您使用数组中数据的地址,这是通过 std::array
的 data()
method 获得的,如下所示:
std::array<double, 2> data2 { 12.3, 45.6 };
double* raw_data2 = data2.data(); // or &(*data2.begin());
std::cout << *raw_data2 << " " << *(raw_data2 + 1) << std::endl;
和这个 compiles and runs fine。但是您可能并不真的需要使用指针算法,只需使用 std::array
.
更好的抽象来编写不同的代码即可
PS - 避免使用 new
和 delete
的显式内存分配(请参阅有关此问题的 C++ Core Guidelines item)。在你的情况下,你根本不需要堆分配——就像你不需要常规数组一样。
当然,这些都是合法的:
template<class T, std::size_t N>
T* alloc_array_as_ptr() {
auto* arr = new std::array<T,N>;
if (!arr) return nullptr;
return arr->data();
}
template<class T, std::size_t N>
T* placement_array_as_ptr( void* ptr ) {
auto* arr = ::new(ptr) std::array<T,N>;
return arr->data();
}
template<std::size_t N, class T>
std::array<T, N>* ptr_as_array( T* in ) {
if (!in) return nullptr;
return reinterpret_cast<std::array<T,N>*>(in); // legal if created with either above 2 functions!
}
// does not delete!
template<std::size_t N, class T>
void destroy_array_as_ptr( T* t ) {
if (!t) return;
ptr_as_array<N>(t)->~std::array<T,N>();
}
// deletes
template<std::size_t N, class T>
void delete_array_as_ptr(T* t) {
delete ptr_as_array<N>(t);
}
令人震惊的是,如果使用得当,上述内容实际上是合法的。指向数组第一个元素的指针是可与整个 std::array.
互换的指针
您必须自己跟踪数组大小。
我不建议这样做。
std::array
毕竟是STL容器!
auto storage = std::array<double, 1 << 20>{};
auto data = storage.begin();
std::cout << *data << " " << *(data + 1) << std::endl;
我想研究如何在指向 std::array class 的元素的指针上使用旧式指针算法。以下代码(也许不足为奇)无法编译:
int main(int argc, char *argv[])
{
double* data1 = new double[(int)std::pow(2,20)];
std::cout << *data1 << " " << *(data1 +1) << std::endl;
delete data1;
data1 = NULL;
double* data2 = new std::array<double, (int)std::pow(2,20)>;
std::cout << *data2 << " " << *(data2 +1) << std::endl;
delete data2;
data2 = NULL;
return 0;
}
作为练习,我想使用所有传统的指针算法,但不是指向旧式双精度数组,而是希望它指向 std::array 的元素。我对这条线的看法:
double* data2 = new std::array<double, (int)std::pow(2,20)>;
是告诉编译器data2是指向分配的堆的第一个元素的指针std::array<double,(int)std::pow(2,20)>
。
我被教导 double* name = new double[size];
意味着 EXACTLY 以下内容:«堆栈为指向 ONE 的指针分配内存double 并将指针命名为 name
,然后堆分配一个大小为 size
的双精度数组,然后将指针设置为指向数组的第一个元素。»由于上面的代码无法编译,我一定是被教了一些不正确的东西,因为相同的语法不适用于 std::arrays.
这提出了几个问题:
- 语句
type* name = new othertype[size];
的实际含义是什么? - 如何使用 std::array 实现我想要的?
- 最后,如何使用 std::unqiue_ptr 和 std::make_unique 实现相同的效果?
I have been taught that the
double* name = new double[size];
means EXACTLY the following: «Stack allocate memory for a pointer to ONE double and name the pointer name, then heap allocate an array of doubles of size size, then set the pointer to point to the first element of the array.» Since the above code does not compile, I must have been taught something incorrect since the same syntax doesnt work for std::arrays.
您的说法是正确的,但请记住,它的工作方式是 new[]
与 new
是不同的运算符。当您动态分配 std::array
时,您正在调用单个对象 new
,并且返回的指针指向 std::array
对象本身。
您可以对 std::array
的内容进行指针运算。例如,data2.data() + 1
是指向 data2[1]
的指针。请注意,您必须调用 .data()
才能获取指向基础数组的指针。
无论如何,不要动态分配 std::array
对象。尽可能避免动态分配,但如果需要,请使用 std::vector
.
您可以使用 data()
成员函数访问 std::array
的 "raw pointer" 视图。但是,std::array
的要点是您 不必 必须这样做:
int main(int argc, char *argv[])
{
std::array<double, 2> myArray;
double* data = myArray.data();
// Note that the builtin a[b] operator is exactly the same as
// doing *(a+b).
// But you can just use the overloaded operator[] of std::array.
// All of these thus print the same thing:
std::cout << *(data) << " " << *(data+1) << std::endl;
std::cout << data[0] << " " << data[1] << std::endl;
std::cout << myArray[0] << " " << myArray[1] << std::endl;
return 0;
}
广义的意思:
type* name = new othertype[size];
最终成为 "I need a variable name
that's a pointer to type
and initialize that with a contiguous allocation of size
instances of othertype
using new[]
"。请注意,这涉及转换,甚至可能无法正常工作,因为 othertype
和 type
可能不支持该操作。 double
的 std::array
不等同于指向 double
的指针。它是一个指向 std::array
句点的指针,但是如果你想假装它是一个 double
并且你不介意你的程序是否由于 未定义的行为 而崩溃你可以继续。你的编译器应该在这里警告你,如果没有,你的警告不够严格。
标准库容器都是关于迭代器,而不是指针,尤其不是指针运算。迭代器比指针更灵活、更强大,它们可以处理链表、树等奇特的数据结构,而不会给调用者带来太多开销。
一些容器,如 std::vector
和 std::array
支持 "random access iterators",这是一种直接访问其内容的类似指针的形式:a[1]
等等。仔细阅读任何给定容器的文档,因为有些容器允许这样做,而许多则不允许。
请记住 "variable" 和 "allocated on stack" 不一定是同一回事。优化编译器可以并将该指针放在任何它想要的地方,包括寄存器而不是内存,或者如果它认为它可以在不破坏代码表达的行为的情况下摆脱它,那么根本就不会放在任何地方。
如果你想要 std::array
,你真的会这样做,因为标准库容器几乎总是比 C 风格的数组更好:
std::array<double, 2> data2;
如果您需要共享此结构,则需要考虑使用 std::unique_ptr
的费用是否值得。这个东西内存占用很小,复制它也很简单,所以搞一个相对昂贵的内存管理功能是没有意义的。
如果您要传递一个更大的结构,请考虑使用引用并将该结构定位在您拥有的最中心的数据结构中,这样就不需要按设计进行复制。
Can we use conventional pointer arithmetic with
std::array
?
是的,当然可以 - 但不能在数组本身上,它是一个对象。相反,您使用数组中数据的地址,这是通过 std::array
的 data()
method 获得的,如下所示:
std::array<double, 2> data2 { 12.3, 45.6 };
double* raw_data2 = data2.data(); // or &(*data2.begin());
std::cout << *raw_data2 << " " << *(raw_data2 + 1) << std::endl;
和这个 compiles and runs fine。但是您可能并不真的需要使用指针算法,只需使用 std::array
.
PS - 避免使用 new
和 delete
的显式内存分配(请参阅有关此问题的 C++ Core Guidelines item)。在你的情况下,你根本不需要堆分配——就像你不需要常规数组一样。
当然,这些都是合法的:
template<class T, std::size_t N>
T* alloc_array_as_ptr() {
auto* arr = new std::array<T,N>;
if (!arr) return nullptr;
return arr->data();
}
template<class T, std::size_t N>
T* placement_array_as_ptr( void* ptr ) {
auto* arr = ::new(ptr) std::array<T,N>;
return arr->data();
}
template<std::size_t N, class T>
std::array<T, N>* ptr_as_array( T* in ) {
if (!in) return nullptr;
return reinterpret_cast<std::array<T,N>*>(in); // legal if created with either above 2 functions!
}
// does not delete!
template<std::size_t N, class T>
void destroy_array_as_ptr( T* t ) {
if (!t) return;
ptr_as_array<N>(t)->~std::array<T,N>();
}
// deletes
template<std::size_t N, class T>
void delete_array_as_ptr(T* t) {
delete ptr_as_array<N>(t);
}
令人震惊的是,如果使用得当,上述内容实际上是合法的。指向数组第一个元素的指针是可与整个 std::array.
互换的指针您必须自己跟踪数组大小。
我不建议这样做。
std::array
毕竟是STL容器!
auto storage = std::array<double, 1 << 20>{};
auto data = storage.begin();
std::cout << *data << " " << *(data + 1) << std::endl;