开发者

Array with undefined size as Class-member

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 MyArrayO开发者_如何学GofInts {
    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 ?


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


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;
}


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;
}


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; 

    }
}


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); }


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜