3

This is a Group class, similar to a Vector. I can create a Vector of Groups and that works fine. I'm having difficulty creating a Groups of Groups. This code compiles and runs but the Group of Groups is not behaving in the same way to a Vector of Groups - see the output. I feel that I'm missing a special constructor in Group which handles templated types? Perhaps it's something else - any pointers gratefully received.

#include <vector>

template <class T>
class Group
{
  private:
    T *data;
    int current_size;
    int max_size;

  private:
    void expand();

  public:
    Group();
    Group(int size);
    ~Group();

    T operator[](int index) const;
    int count() const;
    int add_item(const T new_item);
};

template <class T>
Group<T>::Group()
{
    data = NULL;
    max_size = 0;
    current_size = 0;
}

template <class T>
Group<T>::Group(int size)
{
    if (size < 2)
        size = 2;
    data = new T[size];
    max_size = size;
    current_size = 0;
}

template <class T>
Group<T>::~Group()
{
    if (data != NULL)
        delete[] data;
    current_size = 0;
    max_size = 0;
}

template <class T>
void Group<T>::expand()
{
    if (data == NULL)
    {
        current_size = 0;
        max_size = 2;
        data = new T[2];
    }
    else
    {
        //      printf("expanding %x from %d to %d\n", this, current_size, current_size*2);

        T *tempArray = new T[max_size * 2];
        for (int i = 0; i < max_size; i++)
        {
            tempArray[i] = data[i];
        }

        delete[] data;
        data = tempArray;
        max_size = max_size * 2;
    }
}

template <class T>
int Group<T>::add_item(const T new_item)
{
    // expand the array if necessary
    while (current_size >= (max_size))
        expand();

    // add_item the new thing
    data[current_size] = new_item;
    current_size++;
    return (current_size);
}

template <class T>
inline T Group<T>::operator[](int index) const
{
    return data[index];
}

template <class T>
inline int Group<T>::count() const
{
    return current_size;
}

int main()
{
    // Vector of Groups works fine

    int numgroups = 3; // just 3 groups for testing

    // a vector of Groups
    std::vector<Group<int>> setofgroups(numgroups);

    printf("setofgroups count=%d\n", setofgroups.size());

    // some test data
    // 4 items in first group
    setofgroups[0].add_item(6);
    setofgroups[0].add_item(9);
    setofgroups[0].add_item(15);
    setofgroups[0].add_item(18);

    // one item in second
    setofgroups[1].add_item(7);

    // two items in third
    setofgroups[2].add_item(8);
    setofgroups[2].add_item(25);

    // for each group, print the member values
    for (int g = 0; g < setofgroups.size(); g++)
    {
        printf("group %d\n", g);
        for (int i = 0; i < setofgroups[g].count(); i++)
            printf("  member %d, value %d\n", i, setofgroups[g][i]);
    }

    // Group of groups doesn't seem to work

    Group<Group<int>> groupofgroups(numgroups);

    // this returns ZERO - not 3 as I expected
    printf("groupofgroups count=%d\n", groupofgroups.count());

    groupofgroups[0].add_item(6);
    groupofgroups[0].add_item(9);
    groupofgroups[0].add_item(15);
    groupofgroups[0].add_item(18);

    printf("groupofgroups[0].count=%d\n", groupofgroups[0].count()); // this returns ZERO - where did the items go?

    groupofgroups[1].add_item(7);

    // two items in third
    groupofgroups[2].add_item(8);
    groupofgroups[2].add_item(25);

    // for each group, print the member values
    for (int g = 0; g < groupofgroups.count(); g++)
    {
        printf("group 2  %d (count=%d)\n", g, groupofgroups[g].count());
        for (int i = 0; i < groupofgroups[g].count(); i++)
            printf("  member %d, value %d\n", i, groupofgroups[g][i]);
    }

    return 0;
}

Output:

       setofgroups count=3
    group 0
      member 0, value 6
      member 1, value 9
      member 2, value 15
      member 3, value 18
    group 1
      member 0, value 7
    group 2
      member 0, value 8
      member 1, value 25
    groupofgroups count=0

groupofgroups[0].count=0
Shubham
  • 2,847
  • 4
  • 24
  • 37
rapcoder
  • 31
  • 1
  • 1
    In your Group constructor you set current_size to 0, and you only change it in add_item(), which you never call for the outer Group. – imre May 28 '18 at 13:49
  • 2
    Two tips. (1) `operator T()` is normally better off returning a reference. (2) Look up "rule of three", "rule of five", "rule of zero". – Peter May 28 '18 at 13:49
  • `std::vector` uses _placement new_-operator so that `T` doesn't need to be default-constuctible and copyable when expanding. Consider using it if you want the same behaviour as `std::vector`. – Mikhail Vasilyev May 28 '18 at 15:16

2 Answers2

0

There are 2 things to fix:

  • as suggested in the comment you need to set the current_size in the constructor
  • you need to return a reference with you operator[] else all modification will be applied to a temporary copy of the element

link to the code

Tyker
  • 2,971
  • 9
  • 21
  • Fantastic! - thanks so much all of you. With those changes, it works great. – rapcoder May 28 '18 at 15:01
  • Ummm it runs but it's not right. The design of this is that current_size indicates how many of the array items are valid i.e. you can init a Group initialized with size 10 but current_size indicates that none are valid. If you add say 4 items, current size will be 4 and only [0]-[3] are valid. – rapcoder May 28 '18 at 15:23
  • When the space available runs out, the space is doubled. So - although setting current size to size in the constructor works here it messes up in use elsewhere. Current size is the number of valid/set items, not the maximum space availanble. – rapcoder May 28 '18 at 15:25
  • @rapcoder when you have a size of 4 only elements from 0 to 3 included are valid this is perfectly normal. and if current_size is the number of element you test should check for max_size instead of current_size before setting values – Tyker May 28 '18 at 15:33
0

You have a non trivial destructor in you class. That means that the default copy constructor and default copy assignment will make the wrong thing.

Here is a simple example:

Group<int> a(4);  // ok, ready to add some elements in the freshly allocated a.data
a.add_item(1);
int count = a.add_item(2);   // ok a contains 2 items 1 and 2

if (count == 2) {
    Group<int> b = a;        // the default copy ctor make b.data == a.data...
    std::cout << "b contains " << b.count() << " items" << std::endl; // nice up to here
}             // horror: b reaches end of life and destructor calls delete[](b.data) !

// a.data is now a dangling pointer...

So you have to either implement the copy constructor and the copy assignment operator or mark them explicitely deleted - that is the rule of 3 (more details here)

If you do not implement them, the compiler will implicitely provide a move ctor and a move assignment operator that do not work much greater than the copy version. So you must implement them or mark them explicitely deleted - that is the rule of 5.

And the recommended operator [] for containers - what standard library ones use - is:

T& operator[] (size_t index);              // used for  a[i] = ...
const T& operator[] (size_t index) const;  // used for  x = a[i];

Using references may avoid unnecessary copies of large objects...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • The original code has other stuff but I removed it for this example (thinking the problem was elsewhere). I have: Group(const Group& copyfrom); // copy constructor void operator = (const Group& copyassign); // copy assign constructor – rapcoder May 28 '18 at 15:41
  • These are what I understand as copy and copy-assign (though I may be wrong of course). If I add these in to this example, it still doesn't work. Is there a way to post extra code, rather than inside these little comment boxes? – rapcoder May 28 '18 at 15:44
  • `template inline void Group::operator= (const Group& copyassign ) { int newSize = copyassign.count(); if (max_size < newSize) { if (data != 0) delete[] data; data = new T[newSize]; max_size = newSize; } T* caArray = copyassign.data; for (int i = 0; i < newSize; i++) { data[i] = caArray[i]; } current_size = newSize; } ` – rapcoder May 28 '18 at 15:54
  • `template Group::Group(const Group& copyfrom) { data = 0; max_size = 0; current_size = 0; int newSize = copyfrom.count(); if (max_size < newSize) { if (data != 0) delete[] data; data = new T[newSize]; max_size = newSize; } T* copyArray = copyfrom.data; for (int i = 0; i < newSize; i++) { data[i] = copyArray[i]; } current_size = newSize; } ` – rapcoder May 28 '18 at 15:55