Reputation: 901
I want to work out how to use old style pointer arithmetic on pointers to elements of the std::array class. The following code (unsurprisingly perhaps) does not compile:
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;
}
As an exercise, I want to use all the conventional pointer arithmetic, but instead of pointing at an old style double array, I want it to point to the elements of a std::array. My thinking with this line:
double* data2 = new std::array<double, (int)std::pow(2,20)>;
is to instruct the compiler that data2 is a pointer to the first element of the heap allocated std::array<double,(int)std::pow(2,20)>
.
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.
This raises a couple of questions:
type* name = new othertype[size];
?Upvotes: 0
Views: 2744
Reputation: 11940
std::array
is a STL container after all!
auto storage = std::array<double, 1 << 20>{};
auto data = storage.begin();
std::cout << *data << " " << *(data + 1) << std::endl;
Upvotes: 1
Reputation: 132118
Can we use conventional pointer arithmetic with
std::array
?
Yes, sure you can - but not on the array itself, which is an object. Rather, you use the address of the data within the array, which you get with the std::array
's data()
method, like so:
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;
and this compiles and runs fine. But you probably don't really need to use pointer arithmetic and could just write your code different, utilizing the nicer abstraction of an std::array
.
PS - Avoid using explicit memory allocation with new
and delete
(see the C++ Core Guidelines item about this issue). In your case you don't need heap allocation at all - just like you don't need it with the regular array.
Upvotes: 4
Reputation: 275820
Sure, these are all legal:
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);
}
the above is, shockingly, actually legal if used perfectly. The pointer-to-first-element-of-array is pointer interconvertable with the entire std::array.
You do have to keep track of the array size yourself.
I wouldn't advise doing this.
Upvotes: 1
Reputation: 211690
The meaning of a generalized:
type* name = new othertype[size];
Ends up being "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[]
". Note that this involves casting and might not even work as othertype
and type
might not support that operation. A std::array
of double
is not equivalent to a pointer to double
. It's a pointer to a std::array
, period, but if you want to pretend that's a double
and you don't mind if your program crashes due to undefined behaviour you can proceed. Your compiler should warn you here, and if it doesn't your warnings aren't strict enough.
Standard Library containers are all about iterators, not pointers, and especially not pointer arithmetic. Iterators are far more flexible and capable than pointers, they can handle exotic data structures like linked lists, trees and more without imposing a lot of overhead on the caller.
Some containers like std::vector
and std::array
support "random access iterators" which are a form of direct pointer-like access to their contents: a[1]
and so on. Read the documentation of any given container carefully as some permit this, and many don't.
Remember that "variable" and "allocated on stack" are not necessarily the same thing. An optimizing compiler can and will put that pointer wherever it wants, including registers instead of memory, or nowhere at all if it thinks it can get away with it without breaking the expressed behaviour of your code.
If you want std::array
, which you really do as Standard Library containers are almost always better than C-style arrays:
std::array<double, 2> data2;
If you need to share this structure you'll need to consider if the expense of using std::unique_ptr
is worth it. The memory footprint of this thing will be tiny and copying it will be trivial, so it's pointless to engage a relatively expensive memory management function.
If you're passing around a larger structure, consider using a reference instead and locate the structure in the most central data structure you have so it doesn't need to be copied by design.
Upvotes: 1
Reputation: 23701
You can have access to the "raw pointer" view of std::array
using the data()
member function. However, the point of std::array
is that you don't have to do this:
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;
}
Upvotes: 3
Reputation: 119467
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.
You are correct about that statement, but keep in mind that the way this works is that new[]
is a different operator from new
. When you dynamically allocate an std::array
, you're calling the single-object new
, and the returned pointer points to the std::array
object itself.
You can do pointer arithmetic on the contents of an std::array
. For example, data2.data() + 1
is a pointer to data2[1]
. Note that you have to call .data()
to get a pointer to the underlying array.
Anyway, don't dynamically allocate std::array
objects. Avoid dynamic allocation if possible, but if you need it, then use std::vector
.
Upvotes: 7