andrewf andrewf - 3 months ago 8
C++ Question

Pushing a struct that contains an array into a vector

I am writing a program that performs differential evolution. At one point I am sorting my solutions into an archive of solutions and have run into this problem. My archive is a vector of structs:

std::vector<SingleSolution> archive;

typedef struct SingleSolution
{
int *crop_area;
int *envf;
double nr_cost;
int env_cost;
int front;
int feasible;
} SingleSolution;


When I am doing the sort and have a solution I want to put into my archive, I am creating a SingleSolution and pushing it to the archive:

SingleSolution member;
member.crop_area = (int *)calloc(crops + 1, sizeof(int));
member.envf = (int *)calloc(M + 1, sizeof(int));
member.front = 1;
member.feasible = feasible[member1];
member.nr_cost = nr_costs[member1];
member.env_cost = env_costs[member1];
for (int n = 1; n <= crops; n++) member.crop_area[n] = solution_crop[member1][n];
for (int n = 1; n <= M; n++) member.envf[n] = solution_env[member1][n];

archive.push_back(member);
//free(member.crop_area);
//free(member.envf);


I was freeing those arrays after pushing to the vector, as I thought that it made a copy of what I was pushing in, but in printing the archive I saw what were obviously junk values and realised why. So as you can see I have commented it out.
So my question is, what exactly is happening in that push_back? It it pushing the location in memory where the arrays start, and then I am freeing that space? Any way that I can overcome this, or do I need to be creating the space in the main function and pass it through when I call the sort function, then free it at completion?

Answer

When you do:

archive.push_back(member);

You ask it to copy member to the end of the archive vector using a copy constructor. You can say "there is no copy constructor in SingleSolution", but actually there is - the one provided by the compiler which makes shallow copies of the struct members. This is where your problem resides. You are using raw pointers, and the default copy constructor is only copying the values of the pointers (the memory addresses where they are pointing at - your buffers).

This is a great way to cause undefined behaviour or memory leaks when you try to manage these resources yourself. You realized that when you freed the buffers but still had pointers that were pointing at the old memory.

Let's try to rewrite this piece of code in C++.

First, you don't need that typedef.

Second, your buffers can be replaced with std::vector:

struct SingleSolution
{
    std::vector<int> crop_area;
    std::vector<int> envf;
    double nr_cost;
    int env_cost;
    int front;
    int feasible;
};

To fill your structure, without worrying about memory allocation/deallocation, you can do this:

SingleSolution member;

member.crop_area.resize(crops+1);
member.crop_area[0] = 0;
std::copy(&solution_crop[member1][1], &solution_crop[member1][crops+1], member.crop_area.begin()+1);

member.envf.resize(M+1);
member.envf[0] = 0;
std::copy(&solution_env[member1][1], &solution_env[member1][M+1], member.envf.begin()+1);

member.front = 1;
member.feasible = feasible[member1];
member.nr_cost = nr_costs[member1];
member.env_cost = env_costs[member1];

archive.push_back(member);

Or this:

SingleSolution member;

member.crop_area.reserve(crops+1);
member.crop_area.push_back(0);
std::copy(&solution_crop[member1][1], &solution_crop[member1][crops+1], std::back_inserter(member.crop_area));
// or: std::copy_n(&solution_crop[member1][1], crops, std::back_inserter(member.crop_area));

member.envf.reserve(M+1);
member.envf.push_back(0);
std::copy(&solution_env[member1][1], &solution_env[member1][M+1], std::back_inserter(member.envf));
// or: std::copy_n(&solution_env[member1][1], M, std::back_inserter(member.envf));

member.front = 1;
member.feasible = feasible[member1];
member.nr_cost = nr_costs[member1];
member.env_cost = env_costs[member1];

archive.push_back(member);