0

I am working on a "big" project and I am stuck with a segmentation fault which I finally narrowed to a simpler and reproducible example:

I am going to put two pieces of code. First:

#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{

    std::vector<int> someVector;

    printf("1\n");
    someVector.push_back(1);

    printf("2\n");
    someVector.push_back(2);

    printf("3\n");
    someVector.push_back(3);


    someVector.erase(someVector.begin());
    someVector.erase(someVector.begin());

    return 0;
}

Here, everything works fine. Ok. Now let's try something a little more complicated. Instead of using a vector of ints, lets use a vector of my own defined class.

Note: I'm no expert and I may have made errors when defining the classes. The reason why there is a copy constructor in the class definition is because the parameters variable, is actually a pointer to another class (and not an int), which I copy on the body of the constructor on the real example.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class ActionType {
  public:
    std::string name;
    int type;
    int * parameters;

    ActionType(const std::string _name, const int _type
        );

    ActionType(const ActionType &);   

    ~ActionType(){
        // Direct implementation of destructor
        printf("DELETING\n");
        delete parameters;
    }

  };

 // Implementation of initializer      
 ActionType::ActionType(const std::string _name, const int _type)
        : name(_name)
        , type(_type)
        , parameters(new int(5))
{
}

 // Implementation of copy constructor
 ActionType::ActionType(const ActionType & actionType)
     : name(actionType.name)
      , type(actionType.type)
      , parameters(new int(*actionType.parameters))

{
    printf("COPYING\n");
}

int main()
{
    ActionType actionType("foo", 1);

    std::vector<ActionType> actions;

    printf("1\n");
    actions.push_back(actionType);

    printf("2\n");
    actions.push_back(actionType);

    printf("3\n");
    actions.push_back(actionType);

    actions.erase(actions.begin());
    actions.erase(actions.begin());

    return 0;
}

This, which should be pretty much the same, throws the error:

*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001618c70 ***                             
Aborted (core dumped)

The thing is, in my real example I need a way to delete several items of the vector, and to do that I have something like:

for (int i (...)){
    int indexToDelete = getIndex();
    vector.erase(vector.begin()+indexToDelete);
}

I could also use something like:

std::vector<int> indexes;
for (int i (...)){
    int indexToDelete = getIndex();
    indexes.push_back(indexToDelete);
}
vector.erase(indexes);

But I haven't figured any function that does this. I've seen other answers where they use sort to put all items to remove at the end and after they call pop_back, but it didn't seem super clean.

Anyway, in summary:

  • Does somebody know why when using a class with vector the function erase doesn't work as good?

  • How can I delete several items knowing their indexes in a vector?

halfer
  • 19,824
  • 17
  • 99
  • 186
Alexis
  • 394
  • 4
  • 14
  • 2
    You're missing a copy-assignment operator to complete the [Rule of Three](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). – Miles Budnek May 13 '20 at 03:13
  • The problem is not `std::vector` -- `int main() { ActionType a1("foo", 1); ActionType a2("foo2",2); a2 = a1;}` Boom, you're dead. [See this](http://coliru.stacked-crooked.com/a/87ca4eb51fc0b3f6) – PaulMcKenzie May 13 '20 at 03:15
  • @PaulMcKenzie, don't kill the poor guy for a programming error :) – R Sahu May 13 '20 at 03:24
  • 1
    Your edit doesn't really work, since you have created a memory leak. You need to destroy the old contents in some way, shape or form, and your code doesn't do that. The answer I gave shows how to properly do this, given a correct copy constructor and destructor. – PaulMcKenzie May 13 '20 at 03:30
  • @RSahu -- Well, the program sure died. – PaulMcKenzie May 13 '20 at 03:35

2 Answers2

1

You are missing the assignment operator for your class. The std::vector requires that your class has correct copy semantics, and your ActionType does not have correct copy semantics.

It is simple to add an assignment operator for the class you've written:

#include <algorithm>
//...
class ActionType
{
   //...
   ActionType& operator=(const ActionType &);
   //...
};

ActionType& ActionType::operator=(const ActionType &rhs)
{
    if ( this != &rhs )
    {
        ActionType temp(rhs);
        std::swap(temp.name, name);
        std::swap(temp.type, type);
        std::swap(temp.parameters, parameters);
    }  // <-- The temp is destroyed here, along with the contents.
    return *this;
}

The above uses the copy / swap idiom to implement the assignment operator.

Note that the code now works when the assignment operator is added.

Edit:

The alternate form of the assignment operator could be:

class ActionType
{
   //...
   ActionType& operator=(ActionType);
   //...
};

ActionType& ActionType::operator=(ActionType temp)
{
    std::swap(temp.name, name);
    std::swap(temp.type, type);
    std::swap(temp.parameters, parameters);
    return *this;
}
PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • Thanks! Few questions if you don't mind: 1. Why do you use swap? 2. Why do you create the temp variable instead of using directly rhs? – Alexis May 13 '20 at 03:36
  • If you follow the code, you should conceptually see why `swap` is used. All that's being done is to steal the `temp` object's internals and give `temp` our current internals. When `temp` dies off at the end, it takes with it the old contents. Second, look at my edit -- it basically accomplishes the same thing, with the only difference being that the compiler makes the temporary copy for us when the argument is passed by value. The first version has the test for self-assignment, which *may* give a speedup, depending on the compiler and the class itself. – PaulMcKenzie May 13 '20 at 03:46
  • OK sounds good. I am going to read a little more on swap to understand why it's better than the = operator. Regarding the second question, with the edit it makes more sense. Thanks for everything! – Alexis May 13 '20 at 03:54
0

When you delete the parameters array you need to supply a []

delete [] parameters;

Otherwise you get undefined behavior in your program.

Sidenote > It would probably better to keep it as a std::vector or std::array instead.

AndersK
  • 35,813
  • 6
  • 60
  • 86