简体   繁体   English

在 std::vector 和 std::sort 中使用用户定义的类型

[英]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.我一直在尝试使用用户定义的类型(或类)和 C++ 标准库容器 - 向量。 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.我希望能够使用内置的 C++ 类型、int、float、string 等的向量来做我喜欢做的通常的事情,但是使用我自己定义的类型。 I've written a small example program using a box class to try and understand what is going on.我已经使用box类编写了一个小示例程序来尝试了解正在发生的事情。

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() .使用.push_back()将任意数量的用户定义类型 ( box ) 的新元素添加到向量中。

  2. Once I have stored my elements, I then want to sort them using std::sort with a user defined comparison function.一旦我存储了我的元素,我就想使用std::sort和用户定义的比较函数对它们进行std::sort

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.到目前为止,我编写的代码似乎能够完成目标 1。运行程序后,while 循环之后的 for 循环会打印存储在我的 box 向量中的 25 个框的信息。 However, when I try to sort my boxes using std::sort , and a type_is_less() function:但是,当我尝试使用std::sorttype_is_less()函数对我的盒子进行std::sort

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 ?似乎复制构造函数在push_back()可用,但它会导致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.我尝试在每行之间使用std::cout消息调试复制构造函数,并且复制构造函数的每一行似乎都在执行而不会导致段错误。 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 3 名称

//... //...

//... //...

// program prints the 2 info points for each box // 程序打印每个框的 2 个信息点

61 Lagg // This is the final box info print. 61 Lagg // 这是最后的盒子信息打印。

In type is less.在类型较少。 Comparing box a, width = name with box b, width = Cragg Returning 0比较框 a,宽度 = 名称与框 b,宽度 = Cragg 返回 0

In type is less.在类型较少。 Comparing box a, width = name with box b, width = Lagg Returning 0比较框 a,宽度 = 名称与框 b,宽度 = 滞后返回 0

In type is less.在类型较少。 Comparing box a, width = Cragg with box b, width = Lagg Returning 1比较框 a,宽度 = Cragg 与框 b,宽度 = Lagg 返回 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.我还没有研究的一个悬而未决的问题是我是否可以定义一个类似的类,但使用非指针变量lengthPtrwidthPtr ,并且仍然具有相同的功能。

In reality, your box class need not use any pointers, as the members could simply be non-pointer types.实际上,您的box类不需要使用任何指针,因为成员可能只是非指针类型。

But let's assume you are doing this for experimentation purposes: your box assignment operator has several issues:但是让我们假设您这样做是出于实验目的:您的box赋值运算符有几个问题:

  1. Usage of the wrong form of delete... (it should be delete , not delete[] ).使用错误的delete...形式delete... (应该是delete ,而不是delete[] )。
  2. There is no check for self-assignment of box instances.没有检查box实例的自分配。
  3. If there is an issue with new std::string throwing an exception, you've corrupted your object by changing lengthPtr .如果new std::string抛出异常存在问题,则您已通过更改lengthPtr损坏了对象。

For 1), the fix is simple, and given your test program, will address the issue of the crash:对于 1),修复很简单,并且给定您的测试程序,将解决崩溃问题:

  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.但是,如果要完成box对象的自分配,您的代码将导致未定义的行为。

For 2), a check needs to be done before attempting to recreate the copy:对于 2),在尝试重新创建副本之前需要进行检查:

  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.对于 3),请注意,使用new可能(即使是远程的) new抛出std::bad_alloc异常。 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.如果发生这种情况,并且它出现在new std::string行上,那么您将损坏您的box对象,因为过早更改了lengthPtr

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] .同样,您的示例将是非常罕见的new失败,但如果我们使用一次调用new std::string [x]分配几百万个std::string ,则可能会发生相同的情况。

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).不需要自赋值检查(即使它可以用于优化目的),也不需要检查new抛出,因为没有真正完成对new调用(创建temp会自动抛出我们如果出现问题,请退出)。

The reason for the segmentation fault is in your box& operator=(const box &that) function.分段错误的原因在于您的box& operator=(const box &that)函数。

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. lengthPtrwidthPtr不是用new[]语法创建的。 So, when you try to delete with delete[] you get the segmentation fault.因此,当您尝试使用delete[]删除时,您会遇到分段错误。

To remove segmentation fault from your code, just replace delete[] by delete in your constructor with assignment operator implementation.要从代码中删除分段错误,只需将构造函数中的delete[]替换为delete并使用赋值运算符实现。

Please check this answer too - delete vs delete[] operators in C++也请检查此答案 - C++ 中的 delete 与 delete[] 运算符

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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