简体   繁体   中英

Using user defined types with std::vector and std::sort

I've been trying to get to grips with using user defined types (or classes) with the C++ standard library container - vectors. I want to be able to do the usual things I am comfortable doing with vectors of the in built C++ types, int, float, string, etc.. but with my own defined types. I've written a small example program using a box class to try and understand what is going on.

Here is the code for the class:

class box {

private:
    float *lengthPtr;
    std::string *widthPtr;

public:

    box(float a, std::string b) {
        std::cout<<"Constructor called" << '\n';
        lengthPtr = new float;
        *lengthPtr = a;

        widthPtr = new std::string;
        *widthPtr = b;     
    } 
    // copy constructor 
    box(const box &obj){
        std::cout<< "User defined copy constructor called" << '\n';
        lengthPtr = new float;
        *lengthPtr = obj.check_length();

        widthPtr = new std::string;
        *widthPtr = obj.check_width();

    }
    // copy assignment operator
    box& operator=(const box &that) {
        std::cout<< "Copy assignment operator called";            

            float *localLen = new float;
            *localLen = that.check_length(); 
            delete[] lengthPtr; 
            lengthPtr = localLen;

            std::string *localWid = new std::string;
            *localWid = that.check_width();
            delete[] widthPtr;
            widthPtr = localWid;            

        return *this;
    }

    ~box() {
        std::cout << "User defined destructor called." << '\n';
        delete lengthPtr;
        delete widthPtr;
    }

    float check_length () const {
        return *lengthPtr;
    }

    std::string check_width() const{
        return *widthPtr;
    }

    void set_legnth(const float len) {
        *lengthPtr = len;
    }
    void set_width(const std::string str) {
        *widthPtr = str;      
    }

    void print_box_info(){  
        std::cout << *lengthPtr << " " << *widthPtr << '\n';
    }
};

The two main things I wish to be able to do are:

  1. Add an arbitrary number of new elements of my user defined type ( box ) to a vector using .push_back() .

  2. Once I have stored my elements, I then want to sort them using std::sort with a user defined comparison function.

This is the main function I have been using to test my 2 objectives:

int main() {
    srand(time(NULL));
    int i = 0;
    std::vector<box> boxes;

    while (i<25) {
        int x = rand()%100+1;
        std::cout<< "x = " << x << '\n';

        if ( i < 5)        
            boxes.push_back(box(x, "name"));
        if ( i > 4 && i < 12)
            boxes.push_back(box(x, "Agg"));
        if ( i > 11 && i < 20 )
            boxes.push_back(box(x, "Cragg"));
        if (i>19)
            boxes.push_back(box(x, "Lagg"));

        std::cout << "Added the new box to the collection." << '\n';

        i++;  
    }
    for(unsigned int j = 0; j<boxes.size(); j++) {
            boxes[j].print_box_info();
    }
    std::sort(boxes.begin(), boxes.end(), type_is_less);
}

The code I have written so far seems capable of completing objective 1. After running the program, the for loop after the while loop prints the info of 25 boxes stored in my boxes vector. However, when I try to sort my boxes using std::sort , and a type_is_less() function:

bool type_is_less(const box &a, const box &b) {
    std::cout<<"In type is less." << '\n';
    std::string A = a.check_width();
    std::string B = b.check_width();

    std::cout<< "Comparing box a, width = "  << A << '\n';
    std::cout<< "with box b, width = " << B << '\n'; 
    bool val = A<B;
    std::cout << "Returning " << val <<'\n' <<'\n'; 
    return A<B; 
}

I get a segmentation fault, but I am unsure where the fault is coming from. The user defined copy constructor seems to be the final function called before the seg fault occurs. It seems the copy constructor is usable within push_back() , but it causes problems in std::sort ?

I have tried debugging the copy constructor with std::cout messages in between each line, and every line of the copy constructor seems to execute without causing the seg fault. The seg fault seems to come as soon as the copy constructor finishes executing. The tail end of my console output is below (// I have inserted comments using '//'):

Added the new box to the collection.

3 name

//...

//...

// program prints the 2 info points for each box

61 Lagg // This is the final box info print.

In type is less. Comparing box a, width = name with box b, width = Cragg Returning 0

In type is less. Comparing box a, width = name with box b, width = Lagg Returning 0

In type is less. Comparing box a, width = Cragg with box b, width = Lagg Returning 1

User defined copy constructor called

Segmentation fault (core dumped)

There are a few moving parts here, and I'm not sure how to figure out which part of my code is behaving incorrectly. Everything seems to point to the user defined copy constructor being the culprit, but I'm unsure how to adjust it. Any advice will be much appreciated.

An open question I have yet to investigate is whether I could define a similar class to this, but with non pointer variables lengthPtr , and widthPtr , and still have the same functionality.

In reality, your box class need not use any pointers, as the members could simply be non-pointer types.

But let's assume you are doing this for experimentation purposes: your box assignment operator has several issues:

  1. Usage of the wrong form of delete... (it should be delete , not delete[] ).
  2. There is no check for self-assignment of box instances.
  3. If there is an issue with new std::string throwing an exception, you've corrupted your object by changing lengthPtr .

For 1), the fix is simple, and given your test program, will address the issue of the crash:

  box& operator=(const box& that) {
        std::cout << "Copy assignment operator called";

        float* localLen = new float;
        *localLen = that.check_length();
        delete lengthPtr;  // Correct form of `delete`
        lengthPtr = localLen;

        std::string* localWid = new std::string;
        *localWid = that.check_width();
        delete widthPtr; // Correct form of `delete`
        widthPtr = localWid;

        return *this;
    }

However, your code will cause undefined behavior if a self-assignment of box objects were to be done.

For 2), a check needs to be done before attempting to recreate the copy:

  box& operator=(const box& that) 
  {
        std::cout << "Copy assignment operator called";

        // check if attempting to assign to myself.  If so, just return
        if ( &that == this )
           return *this;

        float* localLen = new float;
        *localLen = that.check_length();
        delete lengthPtr;  // Correct form of `delete`
        lengthPtr = localLen;

        std::string* localWid = new std::string;
        *localWid = that.check_width();
        delete widthPtr; // Correct form of `delete`
        widthPtr = localWid;

        return *this;
    }

For 3), note that using new , it is possible (even though remote), of new throwing a std::bad_alloc exception. If that happens, and it occurs on the new std::string line, you would have corrupted your box object, since the lengthPtr was changed prematurely.

Again, your example would be a very rare occurrence of new failing, but the same scenario could happen if, say we were to allocate a few million std::string 's using one call to new std::string [x] .

To avoid corrupting the object with a failed dynamic memory allocation, you should allocate all needed memory up-front before making any changes to the object itself, and check each allocation (except for the first one) for an exception being thrown. Then if an exception is thrown, you have to rollback the allocated memory you had done previously that was successful.

Here is an example:

box& operator=(const box& that) 
{
    std::cout << "Copy assignment operator called";
    if ( &that == this )
       return *this;

    // Allocate everything first
    float* localLen = new float;  // If this throws, we'll exit anyway.  No harm
    std::string* localWid = nullptr;  
    try 
    {
        localWid = new std::string;  // If this throws exception, need to rollback previous allocation and get out
    }
    catch (std::bad_alloc& e)
    {
       delete localLen;  // rollback previous allocation and rethrow
       throw e;
    }

    // Everything is ok, now make changes
    *localLen = that.check_length();
    delete lengthPtr;
    delete widthPtr;
    lengthPtr = localLen;
    widthPtr = localWid;
    return *this;
}

Overall, that is a lot of work for a correctly working assignment operator.

The good news is that there is a technique that is much easier to code up that addresses all of the issues mentioned, as long as you have a working copy constructor and destructor. That technique is the copy / swap idiom :

 box& operator=(const box& that) 
 {
    std::cout << "Copy assignment operator called";
    box temp(that);
    std::swap(lengthPtr, temp.lengthPtr);
    std::swap(widthPtr, temp.widthPtr);
    return *this;
 } 

There is no need for a self-assignment check (even though it can be used for optimization purposes), and there is no need to check if new throws, as no calls to new are really done (the creation of temp will automatically throw us out if something goes wrong).

The reason for the segmentation fault is in your box& operator=(const box &that) function.

While debugging I found this error-

ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new vs operator delete [])

lengthPtr and widthPtr are not created with new[] syntax. So, when you try to delete with delete[] you get the segmentation fault.

To remove segmentation fault from your code, just replace delete[] by delete in your constructor with assignment operator implementation.

Please check this answer too - delete vs delete[] operators in C++

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM