1

I'm searching for a way to define an array as a class-member with an undefined size (which will be defined on initialization).

class MyArrayOfInts {
    private:
        int[] array;    // should declare the array with an (yet) undefined length

    public:
        MyArrayOfInts(int);
        int Get(int);
        void Set(int, int);
};
MyArrayOfInts::MyArrayOfInts(int length) {
    this->array = int[length];  // defines the array here
}
int MyArrayOfInts::Get(int index) {
    return this->array[index];
}
void MyArrayOfInts:Set(int index, int value) {
    this->array[index] = value;
}

How can I achieve this behaviour ?

Niklas R
  • 16,299
  • 28
  • 108
  • 203

6 Answers6

6

Why not just use std::vector<int>?

Darren Engwirda
  • 6,915
  • 4
  • 26
  • 42
1

Declare it as:

int* array;

Then you can initialize it this way:

MyArrayOfInts::MyArrayOfInts(int length) {
    this->array = new int[length];
}

Don't forget to free the memory in the destrutor:

MyArrayOfInts::~MyArrayOfInts() {
    delete [] this->array;
}
alf
  • 18,372
  • 10
  • 61
  • 92
  • 2
    An don't forget to create a deep copy in the copy constructor. And don't forget to create a deep copy in the copy assignment operator. Now, you've got yourself a buggy and slow `std::vector`. – André Caron Aug 20 '11 at 22:49
  • Going against the Single Responsibility Principle makes kittens cry and code fail. – Matteo Italia Aug 20 '11 at 22:52
  • I just answered what he asked. Maybe he used this class just as an example of what he's trying to do, or maybe he's not allowed to use std::vector for some reason (educational maybe?). – alf Aug 20 '11 at 23:07
  • @alfonso: then he should separate the logic of the "new `vector`" from the logic of the class. The kind of solution you proposed is against the SRP and makes resource management a nightmare, since for *every* class you write that have a member like that you have to remember to implement *correctly* "the big three and a half" (copy constructor, `operator=`, destructor, swap function for the copy&swap idiom), all of this mixing the code that do the actual work of the class and the code to do the resource management. – Matteo Italia Aug 20 '11 at 23:40
1

Is the class declaration complete ? If the constructor of the class takes the size of the array as an argument and you don't want to resize the array, then templatizing the class can give you runtime behaviour.

Now, we don't have to pass the size of the array as argument to the constructor.

template<size_t size>
class MyClass
{
public:

    MyClass()  { std::iota(arr_m, arr_m + size, 1);  }
    int operator[](int index) const
    {
        return arr_m[index];
    }
    int& operator[](int index)
    {
        return arr_m[index];
    }

    void Set(size_t index, int value)
    {
        arr_m[index] = value;
    }

private:
    int arr_m[size];
};

int main()
{
    {
        MyClass<5> obj;
        std::cout << obj[4] << std::endl;
    }
    {
        MyClass<4> obj;
        std::cout << obj[3] << std::endl; 
        obj.Set(3, 30);
        std::cout << obj[3] << std::endl; 

    }
}
André Caron
  • 44,541
  • 12
  • 67
  • 125
Jagannath
  • 3,995
  • 26
  • 30
  • Funny how the critics left your answer alone ;) I just extended my (similar) answer with a demonstration on how to use it with a _runtime-specified_ dimension (slightly less than trivial). +1 for `std::iota` by the way – sehe Aug 21 '11 at 18:54
1

Proof Of Concept

Ok, inspired by UncleBens challenge here, I came up with a Proof-Of-Concept (see below) that let's you actually do:

  srand(123);
  for (int i=0; i<10; i++)
  {
      size_t N = rand() % DEMO_MAX; // capped for demo purposes
      std::auto_ptr<iarray> dyn(make_dynamic_array(N));

      exercise(*dyn);
  }

It revolves around a template trick in factory<>::instantiate that actually uses a compile-time meta-binary-search to match the specified (runtime) dimension to a range of explicit static_array class template instantiations.

I feel the need to repeat that this is not good design, I provide the code sample only to show what the limits are of what can be done - with reasonable effor, to achieve the actual goal of the question. You can see the drawbacks:

  • the compiler is crippled with a boatload of useless statical types and create classes that are so big that they become a performance liability or a reliability hazard (stack allocation anyone? -> we're on 'stack overflow' already :))
  • at DEMO_MAX = 256, g++ -Os will actually emit 258 instantiations of factory<>; g++ -O4 will keep 74 of those, inlining the rest[2]
  • compilation doesn't scale well: at DEMO_MAX = MAX_RAND compilation takes about 2m9s to... run out of memory on a 64-bit 8GB machine; at MAX_RAND>>16 it takes over 25 minutes to possibly compile (?) while nearly running out of memory. It would really require some amounts of ugly manual optimization to remove these limits - I haven't gone so insane as to actually do that work, if you'll excuse me.
  • on the upside, this sample demonstrates the arguably sane range for this class (0..256) and compiles in only 4 seconds and 800Kb on my 64-bit linux. See also a down-scaled, ANSI-proof version at codepad.org

[2] established that with objdump -Ct test | grep instantiate | cut -c62- | sort -k1.10n

Show me the CODE already!

#include <iostream>
#include <memory>
#include <algorithm>
#include <iterator>
#include <stdexcept>

struct iarray
{
    typedef int               value_type;
    typedef value_type*       iterator;
    typedef value_type const* const_iterator;
    typedef value_type&       reference;
    typedef value_type const& const_reference;

    virtual size_t size() const = 0;

    virtual iterator       begin()       = 0;
    virtual const_iterator begin() const = 0;

    // completely unoptimized plumbing just for demonstration purps here
    inline  iterator       end()       { return begin()+size(); }
    inline  const_iterator end() const { return begin()+size(); }
    // boundary checking would be 'gratis' here... for compile-time constant values of 'index'
    inline  const_reference operator[](size_t index) const { return *(begin()+index); }
    inline  reference       operator[](size_t index)       { return *(begin()+index); }
    //
    virtual ~iarray() {}
};

template <size_t N> struct static_array : iarray
{
    static const size_t _size = N;
    value_type data[N];

    virtual size_t size() const { return _size; }
    virtual iterator       begin()       { return data; }
    virtual const_iterator begin() const { return data; }
};

#define DEMO_MAX 256

template <size_t PIVOT=DEMO_MAX/2, size_t MIN=0, size_t MAX=DEMO_MAX>
   struct factory 
   /* this does a binary search in a range of static types
    * 
    * due to the binary search, this will require at most 2log(MAX) levels of
    * recursions.
    *
    * If the parameter (size_t n) is a compile time constant expression,
    * together with automatic inlining, the compiler will be able to optimize
    * this all the way to simply returning
    *     
    *     new static_array<n>()
    *
    * TODO static assert MIN<=PIVOT<=MAX
    */
{
    inline static iarray* instantiate(size_t n)
    {
        if (n>MAX || n<MIN)
            throw std::range_error("unsupported size");
        if (n==PIVOT)
            return new static_array<PIVOT>();
        if (n>PIVOT)
            return factory<(PIVOT + (MAX-PIVOT+1)/2), PIVOT+1, MAX>::instantiate(n);
        else
            return factory<(PIVOT - (PIVOT-MIN+1)/2), MIN, PIVOT-1>::instantiate(n);
    }
};

iarray* make_dynamic_array(size_t n)
{
    return factory<>::instantiate(n);
}

void exercise(iarray& arr)
{
    int gen = 0;
    for (iarray::iterator it=arr.begin(); it!=arr.end(); ++it)
        *it = (gen+=arr.size());

    std::cout << "size " << arr.size() << ":\t";
    std::copy(arr.begin(),  arr.end(),  std::ostream_iterator<int>(std::cout, ","));
    std::cout << std::endl;
}

int main()
{
    {   // boring, oldfashioned method
        static_array<5> i5;
        static_array<17> i17;

        exercise(i5);
        exercise(i17);
    }
    {   // exciting, newfangled, useless method
        for (int n=0; n<=DEMO_MAX; ++n)
        {
            std::auto_ptr<iarray> dyn(make_dynamic_array(n));
            exercise(*dyn);
        }

        try { make_dynamic_array(-1); }           catch (std::range_error e) { std::cout << "range error OK" << std::endl; }
        try { make_dynamic_array(DEMO_MAX + 1); } catch (std::range_error e) { std::cout << "range error OK" << std::endl; }

        return 0;

        srand(123);
        for (int i=0; i<10; i++)
        {
            size_t N = rand() % DEMO_MAX; // capped for demo purposes
            std::auto_ptr<iarray> dyn(make_dynamic_array(N));

            exercise(*dyn);
        }
    }

    return 0;
}
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
0

I'm working on this too for solving a dynamic array problem - I found the answer provided was sufficient to resolve.

This is tricky because arrays in functions from my reading do not continue after function ends, arrays have a lot of strange nuance, however if trying to make a dynamic array without being allowed to use a vector, I believe this is the best approach..

Other approaches such as calling new and delete upon the same pointed to array can/will lead to double free pending the compiler as it causes some undefined behavior.

class arrayObject
{
public:
arrayObject();
~arrayObject();
int   createArray(int firstArray[]);
void  getSize();
void  getValue();
void  deleting();
// private: 
int *array;
int size;
int counter;
int number;
};

arrayObject::arrayObject()
{
this->array = new int[size];
}

arrayObject::~arrayObject()
{
delete [] this->array;
}
0

In response to critics in the comments

I think many people fail to notice a crucial given in the question: since the question asks specifically how to declare an int[N] array inside a struct, it follows that each N will yield a distinct static type to the compiler.

As much as my approach is being 'critiqued' for this property, I did not invent it: it is a requirement from the original question. I can join the chorus saying: "just don't" or "impossible" but as a curious engineer I feel I'm often more helped by defining the boundaries of ust what is in fact still possible.

I'll take a moment to come up with a sketch of an answer to mainly UncleBen interesting challenge. Of course I could hand-waive 'just use template metaprogramming' but it sure would be more convincing and fun to come up with a sample1

1 only to follow that sample with a big warning: don't do this in actual life :)


The TR1 (or c++0x) type std::array does exactly that; you'll need to make the containing class generic to cater for the array size:

template <size_t N> struct MyArrayOfInts : MyArrayOfIntsBase /* for polymorphism */
{
    std::array<int, N> _data;

    explicit MyArrayOfInts(const int data[N])
    {
        std::copy(data, data+N, _data);
    }
};

You can make the thing easier to work with by doing a smart template overloaded factory:

template <size_t N>
    MyArrayOfInts<N> MakeMyArray(const int (&data)[N])
    { return MyArrayOfInts<N>(data); }
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 2
    Er... How does any of this apply to the OP's situation, when the OP clearly indicated that the array size is a *run-time* value and `std::array` is always *compile-time* sized? – AnT stands with Russia Aug 20 '11 at 22:50
  • 1
    Except that you've completely changed the class interface. It's no longer possible to decide the size of the array at run-time and OP specifically requested "define an array as a class-member with an undefined size size, which will be defined on initialization" – André Caron Aug 20 '11 at 22:50
  • Erm... I don't _show_ it but it is entirely possible to create the effect polymorphically (by using that hinted `MyArrayOfIntsBase`; I am aware of the fact that this would sacrifice runtime efficiency at the cost of doing a dubious space efficiency optimization). – sehe Aug 20 '11 at 23:02
  • Suppose the size of the array is determined by `srand(???); size = rand();` How would you now create a MyArrayOfInst from it? A switch covering cases 0...MAX_RAND? – UncleBens Aug 21 '11 at 08:24
  • @AndreyT: Actually, the OP does not clearly indicate that the array size is a runtime value. He merely indicates that the array size will be provided at initialisation; it may still be a compile-time value! – Lightness Races in Orbit Aug 21 '11 at 13:41
  • @Tomalak: I don't wish to presume that he actually _insists_ that he wants to have the `data[]` member _statically_ typed into the struct layout. It _is_ however what he described and I wanted to explore _just how close_ you can get, and what the (major) downsides are :_). For all you skeptics, I added a proof of concept. This is **not** good code, but it _shows_ how this applies to the OP's question, if only by demonstrating why you should just dynamically allocate (preferrably using `std::vector`) – sehe Aug 21 '11 at 18:45