C++ std::sort() 调用析构函数

4

我重载了类的()运算符,用它作为排序比较函数。当使用std::sort()时,它会不知何故多次调用类的析构函数(显然取决于向量中的条目数量)。我在 ~RANK() 中有更详细的描述。

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>

class RANK
{
    struct COMBO
    {
        int x;
    };

    std::vector<COMBO *> data;
public:
    RANK()
    {
        printf("RANK()\n");
    }

    ~RANK()
    {
        printf("~RANK()\n");

        /*
         * Here is the problem.
         * Since my vector consists of pointers to COMBO objects,
         * I delete them upon RANK object's destruction. However,
         * std::sort() calls RANK's destructor many times and
         * throws some runtime error, unless commented out.
         */
        //for (unsigned int i = 0, n = data.size(); i < n; i++)
        //  delete data[i];
    }

    void Add(int x)
    {
        COMBO *combo = new COMBO();
        combo->x = x;

        data.push_back(combo);
    }

    unsigned int Size()
    {
        return data.size();
    }

    void Sort()
    {
        std::sort(data.begin(), data.end(), *this);
    }

    int operator[](unsigned int pos)
    {
        return data[pos]->x;
    }

    bool operator()(COMBO *combo1, COMBO *combo2)
    {
        return combo1->x > combo2->x;
    }
};

int main()
{
    RANK rank;
    rank.Add(1337);
    rank.Add(9001);
    rank.Sort();

    for (unsigned int i = 0, n = rank.Size(); i < n; i++)
        printf("%d ", rank[i]);
        printf("\n");

    system("pause");

    return 0;
}

输出(带注释的析构函数):
RANK()
~RANK()
~RANK()
~RANK()
~RANK()
~RANK()
9001 1337
4个回答

6

在std::sort中,比较函数是以传值的方式传递的。通过使用RANK对象作为比较器,您将向std::sort传递一个副本(作为最后一个值),它可能会在内部复制多次。

我建议将COMBO的比较运算符与RANK类分开。


1
是的,因为所有副本都持有指向相同对象的指针,所以当第二次调用其中一个副本的析构函数时,会出现运行时错误。 - Axel

2
第一个问题是您正在违反三法则。您的类需要一个非平凡的析构函数来释放其资源,因此它需要正确可复制或不可复制以避免多个对象拥有相同的资源。最简单的解决方案是通过删除复制构造函数和复制赋值运算符来防止复制:
RANK(RANK const &) = delete;
void operator=(RANK const &) = delete;

或者,如果您被困在2011年之前的编译器中,请声明它们为私有,但不实现。
另外,您可以考虑存储智能指针,例如std::unique_ptr(以防止复制)或std::shared_ptr(以允许共享所有权);如果这样做,那么您的类将具有与您选择的指针相同的(安全的)复制语义。
防止复制将使第二个问题明显:您正在使用RANK对象作为std::sort的比较器。比较器按值获取,因此对象会被复制到那里。通过为比较器定义单独的类型来轻松解决这个问题。
struct CompareCOMBO {
    bool operator()(COMBO *combo1, COMBO *combo2) {
       return combol1->x > combo2->x;
    }
};

std::sort(data.begin(), data.end(), CompareCOMBO());

或者,如果您可以使用lambda表达式:
std::sort(data.begin(), data.end(), 
    [](COMBO *combo1, COMBO *combo2){
         return combo1->x > combo2->x;
    }
);

添加了一个复制构造函数 - 问题解决了。切换到比较器结构体,这样就不会干扰RANK的析构函数 - 更好了。意识到关于lambda表达式 - 天啊,厉害! - hleV

1
提供一个复制构造函数并在内部放置断点以查看它被调用的位置。

0
通过将*this作为比较对象传递,它会在排序过程中被复制(这就是为什么您不会看到构造函数弹出,如果您有一个复制构造函数,您将看到对其的调用)。
请考虑使用此线程来排序您的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接