我正在尝试重新创建一个用C#编写的优先级队列实现,将C++作为一个要跳入C++的项目,但是很多细微差别使我绊倒了。该队列被设计为可在任何给定类T上工作的模板。该队列将显式地与表示对象(称为优先级对)的结构一起工作:指向T对象的指针和关联的优先级值(int)。

这样做的目的是允许队列中要比较的实际对象(T)完全分开,并且只能指向。我可能并不需要显式的结构来完成此操作,但这就是我正在执行的操作。

队列实现中的重要内容:

template <class T>
class PriorityQueue
{
public:
    PriorityQueue(const int maxSizeIn)
    {
        maxSize = maxSizeIn;
        queueArray = new PriorityPair<T>*[maxSize];
        currentHeapSize = 0;
    }
    ~PriorityQueue()
    {
        cout << "Destroy Queue with size: " << currentHeapSize << endl;
        for (int i = 0; i < currentHeapSize; i++)
        {
            delete (PriorityPair<T>*)queueArray[i];
        }
        delete[] queueArray;
}
private:
    PriorityPair<T>** queueArray;

优先配对的结构:
template <class T>
struct PriorityPair
{
    PriorityPair(int valueIn, T* objectIn)
    {
        _PriorityValue = valueIn;
        _Object = objectIn;
    };

    ~PriorityPair()
    {
         cout << "Destroy Pair for object :(" << *_Object << "):  << endl;
    }

    int _PriorityValue;
    T* _Object;
};

在测试过程中,我发现调用PeekTop方法似乎导致PriorityPair调用其析构函数。我最好的猜测是,由于不了解某种语言的细微差别,我不小心创建了一个临时语言。

这是偷看方法:
    T PeekTop()
    {
        if (IsEmpty())
            return nullptr;
        else
            return *((PriorityPair<T>)(*queueArray[0]))._Object;
    }

此外,这是插入操作(最低有效的插入,不执行堆/队列操作):
    int InsertElement(PriorityPair<T>* elementIn)
    {
        //do not insert nulls --
        if (elementIn == nullptr)
            return -2;
        //we could user std::vector or manually expand the array, but a hard max is probably sufficient
        if (currentHeapSize == maxSize)
        {
            return -1;
        }
        //insert the pointer to the new pair element in at the index corresponding to the current size, then increment the size
        queueArray[currentHeapSize++] = elementIn;
        return 0;
    }

我主要有以下几点:
PriorityQueue<string> queue = PriorityQueue<string>(10);
string s1 = "string1";
int code = queue.InsertElement(new PriorityPair<string>(5, &s1));
string i = queue.PeekTop();
cout << "-------\n";
cout << i << endl;

在确实正确插入元素的范围内,这似乎可行,但是我不知道该对新连接是否按照我的预期运行。当我运行代码时,优先级对的析构函数被调用两次。这在调用PeekTop函数时特别发生。一次在队列的生存期内,一次在队列超出范围并被销毁。

这是上面代码的输出:
Code: 0
Destroy Pair for object :(string1): with priority :(5):
-------
string1
Destroy Queue with size: 1
Destroy Pair for object :(): with priority :(5):

第一个析构函数调用正确显示了带有其值的字符串,但是在第二个中,我们可以看到字符串本身超出了范围(这是可以预期的)。

最佳答案

正如人们在评论中指出的那样,除了以下划线开头的名称之外,您的问题似乎在return *((PriorityPair<T>)(*queueArray[0]))._Object行中。让我们从内而外逐一查看,然后逐步解决。
queueArrayPriorityPair<T>**中声明的PriorityQueue。这可以理解为“指向PriorityPair<T>的指针的指针”,但是在您的情况下,您看起来像是“指向PriorityPair<T>的指针的原始数组”,这也是一个有效的读数。到现在为止还挺好。
queueArray[0]PriorityPair<T>*&或“对PriorityPair<T>的指针的引用”。引用在C++中几乎是看不见的,这仅意味着您要处理的是数组的实际第一个元素,而不是副本。同样,在尝试窥视队列顶部时,这是一个合理的要求。
*queueArray[0]只是PriorityPair<T>&或“对PriorityPair<T>的引用”。同样,此处的引用仅表示您正在处理queueArray[0]指向的实际内容,而不是副本。
(PriorityPair<T>)(*queueArray[0])PriorityPair<T>,将您已经拥有的一个转换为一个新的结果。 这将创建一个临时的PriorityPair<T>,即您稍后看到的销毁的。没有程序性的理由进行这种强制转换(您的IntelliSense问题是一个不同的问题,我对VS的了解不足,无法对此发表评论);它已经是正确的类型。如果将this添加到输出中,则可以验证它是否已被销毁,因为this是指向当前对象的指针,并且临时对象需要驻留在内存中的其他位置。
((PriorityPair<T>)(*queueArray[0]))._ObjectT*或“指向T的指针”。实际上,它指向存储在优先级队列顶部的T,这很好。

最后,完整表达式*((PriorityPair<T>)(*queueArray[0]))._Object对其取消引用以得到T,return语句返回该T的副本。这不会影响您所看到的行为,但是如果您向测试对象添加了析构函数调用,则会如此。通过将返回类型从T更改为TT&,返回对T const&的引用(将放弃复制)可能会更有效。

我注意到的与该问题无关的其他问题,在学习C++时可能会有用(不是完整的 list ;我主要不是在寻找这些问题):

  • 您的两个构造函数都应使用initializer lists并具有空主体(是的,new表达式可以放入初始化列表中,我认为我所说的每个人实际上都是第一次问这个问题,或者包括我在内都认为是错误的)。这样会更有效,更惯用。
  • 您不需要为PriorityPair实现析构函数(除了学习语言的细微差别外);这就是所谓的普通旧数据(POD)类型。如果您希望通过PriorityPair的销毁来对delete进行T,则需要这样做,但是您希望对T进行完全分开的管理。
  • 正如人们在注释中指出的那样,you aren’t allowed to use those identifier names yourself是为了防止编译器或标准库需要它们。这样做可能很好,可能会在编译时给您造成问题,或者可能会正常工作,但会将您所有用户的浏览器历史记录和电子邮件发送给他们的 parent 和/或雇主。最后的可能性很小,但是C++标准并不禁止它。这就是未定义行为的意思。其他允许的行为正在创建一个黑洞来破坏地球和shooting demons out of your nose,但实际上这些可能性很小。
  • 我想您就像正确的PriorityQueue的析构函数一样,很容易弄乱这种事情。恭喜你!
  • 08-19 23:28