How to create an array when the size is a variable not a constant?
Asked Answered
Y

6

5

I've got a method that receives a variable int. That variable constitutes an array size (please, don't offer me a vector). Thus, I need to init a const int inside my method to initialize an array of specific size. Question: how do I do that?

void foo(int variable_int){
    int a[variable_int] = {0}; //error
}
Yean answered 22/1, 2013 at 13:5 Comment(9)
What's wrong with a vector? That failing, I offer you a smart pointer.Our
Why not a vector ? Why even use C++ if you only want to use C idioms ?Icsh
@PaulR,chris: cause for my task I want arrays.Yean
Well, a think of a vector as just an array that you can initialize with a variable size. Are you sure you didn't mean to tag your question "c"?Unearned
I meant "c" instead of C++, not as well...Unearned
@den-javamaniac For most purposes, a C++ vector is at least as good as an array! (If you haven't worked with C++ vectors before: the name is confusing, they behave a lot more like an array than a mathematical vector.) You can even address vector elements with the [] operator if you want (if you don't need/want bounds checking).Koa
The answer is to use a vector. C++ does not support variable-length arrays, and memory management without RAII will lead to memory leaks if anything throws an exception.Bishopric
Why the tag with C? Modern C, from C99 on, can do what you want. So there wouldn't be even a question :)Kelle
Is this question about C or C++? They are two different languages with only minimal overlap. What's considered 'good C' is often 'bad C++', and what's considered 'good C++' often isn't even valid C.Protohistory
M
9
int *a = new int[variable_int];

Remember to delete[] the allocated space when you are done with it!

Milkweed answered 22/1, 2013 at 13:5 Comment(6)
Or better still, std::vector<int> a(variable_int). Now you don't need to remember to delete it, or debug the memory leaks that will otherwise inevitably arise if an exception is thrown.Bishopric
@MikeSeymour Agreed, but not using a vector was a requirement by the question.Milkweed
Sorry, I didn't notice that weird requirement. Good luck debugging the memory leaks then.Bishopric
Another simple way is to alloca, int *a = alloca(variable_int);, which like this answer doesn't initialize the memory to a specific value, but has the benefit of being a very fast allocation scheme: memory is allocated on the stack, as compared to the heap, so you also won't get a memory exception (here is the potential of running out of stack space, but then you've got larger problems). Use this trick for small allocations (up to around a few kilobytes).Calyx
@MikeSeymour - I'm with you: use a vector. Conforming to weird requirements is a time sink.Spratt
And remember to use delete[] not delete! Although it won't matter in this particular instance, since there's no destructor.Byyourleave
R
16

You asked for a non-vector solution but let's go over it because you might have rejected it for the wrong reasons. You shouldn't worry about performance because at the hands of any competent compiler it is going to have pretty much the same overhead as any other standard conformant solution. On the other hand, there are some readability and safety concerns that I'll go over below. Let's look at the ways you can do it from most recommended to least.

std::vector

The community's favorite container and for good reason. Not only can it be declared with a run-time size, but the size can be changed at any time. This facilitates use when size cannot be predetermined, eg when repeatedly polling for user input. Examples:

// Known size
size_t n;
std::cin >> n;
std::vector<int> vec(n);

// Unknown size
std::vector<int> vec;
int input;
while (std::cin >> input) { // Note: not always the best way to read input
    vec.push_back(in);
}

There's not much downside to using std::vector. The known size case requires exactly one dynamic allocation. The unknown size requires more in the general case, but you wouldn't be able to do any better anyway. So performance is more or less optimal.

Semantically, it might not be ideal for sizes that are constant throughout the execution. It might not be apparent to the reader that this container is not intended to change. It is not known to the compiler either so it will allow you to do something "wrong" like push_back into a vector, even though conceptually it's supposed to have a constant size.

std::unique_ptr (or std::shared_ptr)

The safest solution if enforcing static size is important to you.

size_t n;
std::cin >> n;
auto arr = std::make_unique<int[]>(n);

arr's size cannot change, though it can be made to release the current array and point to another one of different size. Therefore, if logically the size of your container is constant, this conveys intent in a clearer way. Unfortunately, it is also much weaker than std::vector even in the constant-size case. It is not size-aware, so you have to explicitly store the size. For the same reason it does not offer iterators and can't be used in range for loops. It is up to you (and the project in question) if you want to sacrifice these features to enforce static size.

Initially I had recommended boost::scoped_array but after further thought I don't believe it has much to offer over this solution so I'll stick to the standard library.

new[] - delete[]

Technically a solution, but unless you are forced to use an old C++ standard or you are writing a low-level library that manages memory internally they are strictly worse than the std::unique_ptr or std::shared_ptr solution. They offer no more features, but are significantly less safe because you have to explicitly free the memory when you're done with it. Otherwise, you will leak it and this might cause significant problems. To make matters worse, using delete[] properly can be non-trivial for programs with complicated flows of execution and exception handling. Please don't use this when the above solutions are available to you!

size_t n;
std::cin >> n;
int* arr = new int[n];
...
// Control flow must reach exactly one corresponding delete[] !!!
delete[] arr;

Bonus: Compiler extension

Some compilers might actually be ok with the following code

size_t n;
std::cin >> n;
int arr[n];

Relying on this has severe drawbacks. Your code cannot be compiled on all C++ conformant compilers. It probably doesn't even compile on all versions of the given compiler. Also, I doubt that the produced executable checks the value of n and allocates on the heap when needed meaning you can blow up your stack. This solution only makes sense when you know the upper bound of n is small and when performance is so important to you that you're willing to rely on compiler-specific behavior to get it. These are truly exceptional cases.

Raddy answered 3/10, 2017 at 22:0 Comment(0)
M
9
int *a = new int[variable_int];

Remember to delete[] the allocated space when you are done with it!

Milkweed answered 22/1, 2013 at 13:5 Comment(6)
Or better still, std::vector<int> a(variable_int). Now you don't need to remember to delete it, or debug the memory leaks that will otherwise inevitably arise if an exception is thrown.Bishopric
@MikeSeymour Agreed, but not using a vector was a requirement by the question.Milkweed
Sorry, I didn't notice that weird requirement. Good luck debugging the memory leaks then.Bishopric
Another simple way is to alloca, int *a = alloca(variable_int);, which like this answer doesn't initialize the memory to a specific value, but has the benefit of being a very fast allocation scheme: memory is allocated on the stack, as compared to the heap, so you also won't get a memory exception (here is the potential of running out of stack space, but then you've got larger problems). Use this trick for small allocations (up to around a few kilobytes).Calyx
@MikeSeymour - I'm with you: use a vector. Conforming to weird requirements is a time sink.Spratt
And remember to use delete[] not delete! Although it won't matter in this particular instance, since there's no destructor.Byyourleave
B
4

C++ does not support variable-length arrays. Instead, you'll need to allocate the array dynamically:

std::vector<int> a(variable_int);

or since you say you don't want to use a vector for some reason:

class not_a_vector
{
public:
    explicit not_a_vector(size_t size) : a(new int[size]()) {}
    ~not_a_vector() {delete [] a;}
    int & operator[](size_t i) {return a[i];}
    int   operator[](size_t i) const {return a[i];}

    not_a_vector(not_a_vector const &) = delete;
    void operator=(not_a_vector const &) = delete;

private:
    int * a;
};

not_a_vector a(variable_int);

UPDATE: The question has just been updated with the "C" tag as well as "C++". C (since 1999) does support variable-length arrays, so your code should be fine in that language.

Bishopric answered 22/1, 2013 at 13:14 Comment(0)
P
3

You can easily make a const variable from a non-const variable by writing const int bar = variable_int; - however that won't help you. In C++ the size of an array with automatic storage must be a compile-time constant. You can't turn a variable into a compile-time constant, so what you want is simply not possible.

Depending on your needs, you could make a a pointer and allocate memory using new (and then later delete it) or, if the parameter to foo will always be known at compile-time, you could turn foo into a template function like this:

template<int n> void foo() {
    int a[n] = {0};
}
Pekan answered 22/1, 2013 at 13:11 Comment(0)
H
2

To do what you want, you will need to use dynamic allocation. In which case I would seriously suggest using vector instead - it is the "right" thing to do in C++.

But if you still don't want to use vector [why you wouldn't is beyond me], the correct code is:

 void foo(int variable_int){
    int *a   = new int[variable_int]();   // Parenthesis to initialize to zero.
    ... do stuff with a ... 
    delete [] a;
 }

As others have suggest, you can also use calloc, which has the same effect of initializing to zero, but not really the "c++" solution.

Hexastich answered 22/1, 2013 at 13:12 Comment(2)
Don't forget try catch for exception safety ;-) It's always worth rubbing in just how much legwork std::vector does for you!Unearned
Thanks for the parenthesis, I got segfault without em. :-)Yean
S
0

If you're using arrays it's a good idea to encapsulate them:

template<typename Type>
class Vector {
    //...
};

The standard library comes with an implementation: std::vector

Sentient answered 22/1, 2013 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.