繁体   English   中英

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

[英]Using user defined types with std::vector and std::sort

我一直在尝试使用用户定义的类型(或类)和 C++ 标准库容器 - 向量。 我希望能够使用内置的 C++ 类型、int、float、string 等的向量来做我喜欢做的通常的事情,但是使用我自己定义的类型。 我已经使用box类编写了一个小示例程序来尝试了解正在发生的事情。

这是该类的代码:

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';
    }
};

我希望能够做的两件主要事情是:

  1. 使用.push_back()将任意数量的用户定义类型 ( box ) 的新元素添加到向量中。

  2. 一旦我存储了我的元素,我就想使用std::sort和用户定义的比较函数对它们进行std::sort

这是我用来测试我的两个目标的主要功能:

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);
}

到目前为止,我编写的代码似乎能够完成目标 1。运行程序后,while 循环之后的 for 循环会打印存储在我的 box 向量中的 25 个框的信息。 但是,当我尝试使用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; 
}

我收到分段错误,但我不确定错误来自何处。 用户定义的复制构造函数似乎是在段错误发生之前调用的最终函数。 似乎复制构造函数在push_back()可用,但它会导致std::sort出现问题?

我尝试在每行之间使用std::cout消息调试复制构造函数,并且复制构造函数的每一行似乎都在执行而不会导致段错误。 一旦复制构造函数完成执行,段错误似乎就会出现。 我的控制台输出的尾端如下(// 我使用“//”插入了注释):

将新盒子添加到收藏中。

3 名称

//...

//...

// 程序打印每个框的 2 个信息点

61 Lagg // 这是最后的盒子信息打印。

在类型较少。 比较框 a,宽度 = 名称与框 b,宽度 = Cragg 返回 0

在类型较少。 比较框 a,宽度 = 名称与框 b,宽度 = 滞后返回 0

在类型较少。 比较框 a,宽度 = Cragg 与框 b,宽度 = Lagg 返回 1

用户定义的复制构造函数调用

分段错误(核心转储)

这里有一些活动部分,我不确定如何确定我的代码的哪一部分行为不正确。 一切似乎都指向用户定义的复制构造函数是罪魁祸首,但我不确定如何调整它。 任何建议将不胜感激。

我还没有研究的一个悬而未决的问题是我是否可以定义一个类似的类,但使用非指针变量lengthPtrwidthPtr ,并且仍然具有相同的功能。

实际上,您的box类不需要使用任何指针,因为成员可能只是非指针类型。

但是让我们假设您这样做是出于实验目的:您的box赋值运算符有几个问题:

  1. 使用错误的delete...形式delete... (应该是delete ,而不是delete[] )。
  2. 没有检查box实例的自分配。
  3. 如果new std::string抛出异常存在问题,则您已通过更改lengthPtr损坏了对象。

对于 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;
    }

但是,如果要完成box对象的自分配,您的代码将导致未定义的行为。

对于 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;
    }

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

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

为了避免因动态内存分配失败而损坏对象,您应该在对对象本身进行任何更改之前预先分配所有需要的内存,并检查每个分配(第一个分配除外)是否抛出异常。 然后,如果抛出一个异常,你必须回滚分配的内存,你以前做过成功。

下面是一个例子:

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;
}

总的来说,对于正确工作的赋值运算符来说,这是很多工作。

好消息是,只要您有一个工作副本构造函数和析构函数,就有一种更容易编码的技术可以解决所有提到的问题。 该技术是复制/交换习语

 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;
 } 

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

分段错误的原因在于您的box& operator=(const box &that)函数。

在调试时我发现了这个错误-

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

lengthPtrwidthPtr不是用new[]语法创建的。 因此,当您尝试使用delete[]删除时,您会遇到分段错误。

要从代码中删除分段错误,只需将构造函数中的delete[]替换为delete并使用赋值运算符实现。

也请检查此答案 - C++ 中的 delete 与 delete[] 运算符

暂无
暂无

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

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