2

I want to have a class that contains an array as it's data member. The size of the array is declared during construction. I know that the size of an array needs to be known at compile-time, but is there no way to work around this by using a const int to define the size and subsequently using constructor initializer list? I'm not allowed to use vectors. This is my futile attempt:

#include <iostream>

using namespace std;

class myArray {
    public:
        myArray(int size) : capacity(size) {}
    private:
        const int capacity;
        int a[capacity];
};

int main() {
    myArray ar(25); 
    return 0;     
}

It gives the following error: 'capacity' was not declared in this scope

Tim Southee
  • 51
  • 2
  • 6
  • This is a challenge. I clearly mentioned that we're not allowed to use vectors. Please don't ask why because it's a way and a challenge to understand arrays better. – Tim Southee Mar 10 '15 at 01:11
  • @paxdiablo Oh, that's my fault. – fefe Mar 10 '15 at 01:15
  • I suspect that size of array has to be, or is required by the challenge, to be runtime assigned. Is there a template version answer to this question? Practically speaking, containers of arrays usually do have their sizes known, by design, at compile time. In those cases, we only need to somehow exposes the size to the interface of class so that it can be configured when the class is instantiated. I'm new to template but I guess moving size from constructor argument to template argument should also work. – user3528438 Mar 10 '15 at 02:11
  • As said in some of the answers, you must use some sort of pointer. The _reason_ for this is that the memory layout of the object must be known at compile time, and a array data member is part of the size of the object size. That is, consider class A and B which have member arrays int a[1] int b[2]. Then sizeof(A) will be smaller than sizeof(B). Further, the compiler needs this static size should you ever want to construct, say, an array of myArray objects. I hope that offers some rational for the limitation. A pointer to an array (or just a pointer) is constant size, sizeof(int *), etc. – Sam Mar 10 '15 at 03:11

7 Answers7

4

Try to use pointer instead

#include <iostream>
using namespace std;
class myArray {
public:
    myArray(int size) : capacity(size) {array = new int[capacity];}
    ~myArray() {delete [] array;}

private:
    const int capacity;
    int* array;
};

int main() {
    myArray ar(25); 
    return 0;     
}

And don't forget to release in the destructor.

YaleCheung
  • 630
  • 3
  • 9
  • Yes, this seems to be a popular answer. Can you please explain what's wrong with the original code? – Tim Southee Mar 10 '15 at 01:16
  • 1
    In your original code, you cannot get the size of array until runtime. Array requires that the size is fixed during compile time. – YaleCheung Mar 10 '15 at 01:25
3

All of the above answers are "technically correct", but none actually show the usage of a smart pointer which makes all of the additional memory management unnecessary.

class my_array
{
public:
   my_array (size_t sz)
     : arr {new int [sz]} {}

pirvate:
   std::unique_ptr <int[]> arr;
};

Now you needn't concern yourself with the rule of (3 or 5), and the code is correct and (mostly) exception safe.

Chad
  • 18,706
  • 4
  • 46
  • 63
1

It could work with a static const but you would lose the constructor argument setting. You have to allocate it dynamically for it to work:

class myArray {
    public:
        myArray(int size) : capacity(size) 
        {
            a = malloc(size * sizeof(int));
        }
        ~myArray() { free(a); }
    private:
        int capacity;
        int* a;
};

int main() {
    myArray ar(25); 
    return 0;     
}

Although allocating with new [] and freeing with delete [] is probably more C++ than malloc and free.

As stated in some comments, if you dynamically allocate and don't use automatic resource management objects like shared_ptr or unique_ptr, you have to follow the rule of three.

Eric Fortin
  • 7,533
  • 2
  • 25
  • 33
  • Yes, this seems to be a popular answer. Can you please explain what's wrong with the original code? – Tim Southee Mar 10 '15 at 01:17
  • I would almost say _never_ use `malloc/free` in C++ code, it's one of those things that are for legacy support only. It won't throw exceptions on out-of-memory so you have to manage that yourself unless you want dodgy programs. You should also cast `malloc` return value lest the compiler complain bitterly. – paxdiablo Mar 10 '15 at 01:43
1

The problem with your original class is that it allows for different values to be passed in as the capacity. Hence, you cannot create an array with that value, in the way you want. It makes no difference that you only create one instance with a size of 25, that's a property of the program, and the class itself doesn't know it's only used that way.


Now I'm not going to question why you can't use a vector but it seems a shame to not use the full capabilities of the language/library.

However, given your restrictions, you can create an array dynamically rather than trying to create a fixed array:

#include <iostream>

using namespace std;

class myArray {
    public:
        myArray(int size) : capacity(size) {
            a = new int[capacity];
        }
        ~myArray() {
            delete[] a;
        }
        // Also need all those other things, mandated by the
        // rule of 3/5, to allow proper deep copy/move:
        // - copy constructor.
        // - copy assignment operator.
        // - move constructor (C++11).
        // - move assignment operator (C++11).

    private:
        const int capacity;
        int *a;
};

int main() {
    myArray ar1(25);
    myArray ar1(42);
    return 0;
}
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Thanks. Does the array HAVE to be dynamically allocated on the heap? – Tim Southee Mar 10 '15 at 01:19
  • There would be problem when `myArray` is copied. `a` would be deleted twice. – fefe Mar 10 '15 at 01:19
  • @fefe: yes. The intent was simply to show the changes needed to turn the array into a dynamic one, rather than provide a full, bullet-proof application. I've added comments to indicate other work that should be done. – paxdiablo Mar 10 '15 at 01:29
0

By passing in capacity as a variable, you have a dynamically sized array (whose size is only known at runtime). This means that sizeof(myArray) is unknown to the compiler, which will cause problems.

Instead, you will need to store a pointer to an array.

Another (ugly) approach would be to use a placement strategy, place the array at the end of the struct/class, and give it a constant size of 1, as shown here

mrks
  • 8,033
  • 1
  • 33
  • 62
  • Hmmm...like allocating the array on heap instead? Nice suggestion. But, can you please explain how the size is only known at runtime in my implementation? I mean, the size is provided as a constructor argument when the object is created. So, it is known already as opposed to doing something like taking the size as console input during the run-time. – Tim Southee Mar 10 '15 at 01:14
  • But you could also have `myArray ar2(25)`. Now `sizeof(ar) != sizeof(ar2)`. The size of array members has to be constant. – mrks Mar 10 '15 at 01:17
  • But, `ar2` is a new instance altogether. The size of array is constant within an instance. – Tim Southee Mar 10 '15 at 01:23
0

constness is insufficient, because you can even take a value that's provided by the user during execution of the program and make it const:

int x;
std::cin >> x;
const int y = x;

It's insufficient because the array dimensions must be known when the program is compiled.

Historically, if you were to initialise a static const "variable" at the point of declaration, then this would be enough to convince the compiler that it could use that value for an array dimension:

static const unsigned int N = 5;

because there's no way that can go wrong.

Nowadays, we have constexpr for the purpose of making this absolutely explicit.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
0

You can use Template non-type arguments (only works for C++). Take a look at this page and example:

https://www.ibm.com/docs/en/zos/2.1.0?topic=arguments-template-non-type.

ouflak
  • 2,458
  • 10
  • 44
  • 49
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 02 '21 at 10:21
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/30237349) – Emi OB Nov 02 '21 at 13:40