0

I was practicing with C++ vectors, and found a problem when I was inserting elements into a 2D vector. In the following example:

#include <iostream>
#include <vector>

void fillVector(std::vector<std::vector<int> > &vec) {
    std::vector<int> list1;
    vec.push_back(list1);
    list1.push_back(1);
    list1.push_back(2);

    std::vector<int> list2;
    list2.push_back(3);
    list2.push_back(4);
    vec.push_back(list2);

    return;
}

int main() {
    std::vector<std::vector<int> > vect;
    fillVector(vect);

    std::cout << "vect size: " << vect.size() << std::endl;

    for(int i = 0; i < vect.size(); i++) {
        std::cout << "vect in size: " << vect.at(i).size() << std::endl;
    }
}

the size of the first inner-list is 0, and the size of the second inner-list is 2. The only difference between list1 and list2 is that list1 is first inserted into the vec 2D vector, before elements are inserted into it, while elements are first inserted into list2, before it is itself inserted into the 2D vector. After returning from the function, the elements inserted into list1 are not printed, and its size remains the same.

I also attempted the first method with pointers instead,

std::vector<int> *list3 = new std::vector<int>();
vec.push_back(*list3);
list3->push_back(5);
list3->push_back(6);

However, the size of list3 when read from the calling function is still 0. I don't understand the difference between the two approaches. Why does the list have to appended after it's elements are inserted?

sparkonhdfs
  • 1,313
  • 2
  • 17
  • 31
  • 3
    In first case, you need to change `list1.push_back(1); list1.push_back(2);` to `vec.back().push_back(1); vec.back().push_back(2);` otherwise you just add items to `list1` not `vec`. – DimChtz Aug 05 '16 at 00:24
  • Btw, your third solution (pointers) essentially doesn't work for the same reason first case doesn't work, since you `push_back()` the dereference of `list3` (just the values of list3). – DimChtz Aug 05 '16 at 00:32
  • Expanding on @DimChtz comment: when you put something into a `vector`, the `vector` stores a copy. Manipulating the original will have no effect on the copy. – user4581301 Aug 05 '16 at 00:40

3 Answers3

2

It almost seems like you are expecting python-like behavior? In any case, in C++ the distinction between references, pointers, and values is very important.

Your fillVector function has the right idea, as it takes a reference to a 2D vector std::vector<std::vector<int> > &vec - notice the &. However, when you create list1, and use push_back() right away

std::vector<int> list1;
vec.push_back(list1);

you are pushing the empty vector. push_back() will create a copy of this vector, which will be contained in vect (in main), and is a completely separate vector from list1.

At this point, if you want to access the vector already pushed, you can use back(), which returns a reference to the last element in the vector vec, that is, the last one pushed.

vec.back().push_back(1);
vec.back().push_back(2);

list2 you modify before pushing back, so when the copy is made, it is made of the already modified vector. Your attempt with list3 doens't really change things much, you dereference the pointer when you push_back() and a copy is made all the same. You could make vect be std::vector<std::vector<int>*>, but I'd strongly advice against it, as you have to do manual memory management - using new.

Note: While it's important for you to learn at some point, you should really try to avoid using pointers whenever possible, specially RAW pointers (look at smart pointers instead). std::vector, and all other std containers I know of, do their own memory management - they are sure to do it more efficiently than you, and BUG FREE.


I would suggest that you simply work on the last vector pushed, as such:

void fillVector(std::vector<std::vector<int> > &vec) {
    vec.push_back(std::vector<int>());
    vec.back().push_back(1);
    vec.back().push_back(2);

    vec.push_back(std::vector<int>());
    vec.back().push_back(3);
    vec.back().push_back(4);
    return;
}

as you can see it's pretty much the same code repeated twice, so you can easily loop to get this or other results.

Ramon
  • 1,169
  • 11
  • 25
1

vector.push_back(var) makes a copy of var and inserts it into the vector. If you use push_back() on an empty list, it copies the empty list into the vector. Changing values in the list after this does not affect the copy that was inserted into the vector. This is because you are passing an actual object to push_back(), not a pointer to an object.

In the third example, you take a step in the right direction, but you de-reference the list before you pass it in, so push_back() makes a copy of what is at that address.

A simple solution to the problem is to always set your values before you insert the list into the vector.

If you wish to be able to change the values after the list is inserted, use vect.at(i).push_back(val) to add a value to the list at i. You could also make the vector contain pointers to other vectors, rather than the vectors themselves:

void fillVector(std::vector<std::vector<int> *> &vec) {
    std::vector<int> *list1 = new std::vector<int>(); //Remember to allocate memory since we're using pointers now
    list1->push_back(1);
    list1->push_back(2); 
    vec.push_back(list1); // Copy the pointer that is list1 into vec

    std::vector<int> *list2 = new std::vector<int>();
    vec.push_back(list2); // Copy the pointer that is list2 into vec
    list2->push_back(3);
    list2->push_back(4);
    return;
}

int main() {
    std::vector<std::vector<int> *> vect; // Vector of pointers to vectors
    fillVector(vect);

    std::cout << "vect size: " << vect.size() << std::endl;

    for(int i = 0; i < vect.size(); i++) {
        std::cout << "vect in size: " << vect.at(i)->size() << std::endl;
    }
}
std::vector<std::vector<int> *> vec = new; // Vector of pointers
naffarn
  • 526
  • 5
  • 12
  • Thank you for explaining it so well. I like the solution of using pointers, they seem to be safer in this instance. – sparkonhdfs Aug 05 '16 at 01:04
  • No worries, remember to call `delete` when you finish, since they're allocated with `new` – naffarn Aug 05 '16 at 01:10
  • 1
    @iamseiko They aren't. Pointers in `vector`s is inviting a world of hell if you aren't prepared. – user4581301 Aug 05 '16 at 01:11
  • 1
    While it does work, I'd strongly suggest that you avoid this solution, because it requires that you do manual memory management - using `new`, which is a really big no-no. – Ramon Aug 05 '16 at 01:11
  • If you're on c++11, you can use smart pointers (e.g std::unique_ptr) to avoid needing to take care deletion, but it is much simpler to operate on the vector elements directly. – naffarn Aug 05 '16 at 01:15
0

When you put something into a std::vector, the vector stores a copy. Manipulating the original will have no effect on the copy. If you put a pointer into a vector of pointers the vector still stores a copy of the pointer. Both the original and the copy in the vector point at the same memory, so you can manipulate the referenced data and see a change in the referenced data.

So...

std::vector<int> list1;
vec.push_back(list1);
list1.push_back(1);
list1.push_back(2);

puts a copy of the emptylist1 into vec. Then copies of 1 and 2 are placed into the original list1. The copy of list1 in vec is unaffected.

Writing this as

std::vector<int> list1;
vec.push_back(list1);
vec.back().push_back(1);
vec.back().push_back(2);

will correct this. As will a slightly cleaner version

vec.push_back(std::vector<int>());
vec.back().push_back(1);
vec.back().push_back(2);

as it doesn't have a waste list1 hanging around cluttering up the scope.

And

vec.push_back(std::vector<int>{1,2});

will simplify even further if your compiler supports C++11 or better.

On the other hand...

std::vector<int> list2;
list2.push_back(3);
list2.push_back(4);
vec.push_back(list2);

puts copies of 3 and 4 into list2 and then puts a copy of list2, complete with copies of the copies of 3 and 4.

Similar to above,

std::vector<int> list2{3,4};
vec.push_back(list2);

can reduce the workload.

Unfortunately your experiment with list3 fails because while list3 is a pointer vec does not hold a pointer, so list3 is dereferenced and the vector referenced is copied. No pointer to the data list3 references is stored, and vec contains an empty vector for the same reason as above.

std::vector<int> *list3 = new std::vector<int>();
vec.push_back(*list3);
list3->push_back(5);
list3->push_back(6);

A few notes on storing pointers in vectors

  1. The vector only stores a copy of the pointer. The data pointed at must be scoped in such a way that it will not be destroyed before the vector is done with it. One solution is to dynamically allocate the storage.
  2. (This applies to dynamically allocated pointers in general) If you dynamically allocate, sooner or later someone has to clean up the mess and delete those pointers. Look into storing smart pointers rather than raw pointers and not storing pointers at all.
  3. Familiarize yourself with the Rule of Three. vector looks after itself, but if you have two copies of the vector and you remove and delete a pointer from only one of them, you're going to have some debugging to do.
Community
  • 1
  • 1
user4581301
  • 33,082
  • 7
  • 33
  • 54