簡體   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