简体   繁体   English

从指针返回对象时发生意外的析构函数调用

[英]Unexpected destructor call when returning object from a pointer

I'm trying to recreate a priority queue implementation I made in C# to C++ as a project to jump into C++ with, but a lot of the nuance is tripping me up. 我正在尝试重新创建我在C#中制作的优先级队列实现,作为一个要跳入C ++的项目,但是很多细微的差别使我绊倒了。 The queue is designed as a template to work on any given class T. The queue will explicitly work with a struct representing objects called Priority Pairs: a pointer to a T object and an associated priority value (int). 该队列被设计为可在任何给定的类T上工作的模板。该队列将显式地与表示对象的结构一起使用,该对象表示优先级对:指向T对象的指针和关联的优先级值(int)。

The goal of this is to allow the actual objects (the T's) being compared in the queue to be completely separate and only ever be pointed to. 这样做的目的是使队列中要比较的实际对象(T)完全分开,并且只能指向。 I probably don't explicitly need the struct to accomplish this but this is how I'm doing it. 我可能并不需要显式的结构来完成此操作,但这就是我正在执行的操作。

The important bits from the queue implementation: 队列实现的重要内容:

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;

The Struct for PriorityPair: 优先配对的结构:

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

During the course of my testing, I've found that calling my PeekTop method seems to cause a PriorityPair to have its destructor called. 在测试过程中,我发现调用PeekTop方法似乎导致PriorityPair调用其析构函数。 My best guess is that I am accidentally creating a temporary one due to a failure to understand some nuance of the language. 我最好的猜测是,由于无法理解某种语言的细微差别,我意外地创建了一个临时语言。

Here's the peek method: 这是偷看方法:

    T PeekTop()
    {
        if (IsEmpty())
            return nullptr;
        else
            return *((PriorityPair<T>)(*queueArray[0]))._Object; 
    }

Additionally, here is the insert operation (minimally effective insert, does no heap/queue operations): 此外,这是插入操作(最低有效的插入,不执行堆/队列操作):

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

In the main I have the following: 我主要有以下几点:

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;

This appears to work, insofar that it does insert the element correctly, but I don't understand if that new pair is behaving as I intend it to. 在确实正确插入元素的范围内,这似乎可行,但是我不知道该对新连接是否按照我的预期运行。 When I run the code the destructor for my Priority Pair is getting called twice. 当我运行代码时,优先级对的析构函数被调用两次。 This happens specifically when the function PeekTop is called. 这在调用PeekTop函数时特别发生。 Once during the queue's lifetime and once when the queue goes out of scope and is destroyed. 一次在队列的生存期内,一次在队列超出范围并被销毁时。

Here is the output from the above code: 这是上面代码的输出:

Code: 0
Destroy Pair for object :(string1): with priority :(5):
-------
string1
Destroy Queue with size: 1
Destroy Pair for object :(): with priority :(5):

The first destructor call shows the string correctly with its value, but in the second we can see that the string itself has gone out of scope (which is fine and expected). 第一个析构函数调用正确地显示了字符串及其值,但是在第二个中,我们可以看到字符串本身超出了范围(这是可以预期的)。

Aside from the names starting with an underscore, which as people have pointed out in the comments, your problem appears to be in the line return *((PriorityPair<T>)(*queueArray[0]))._Object . 正如人们在注释中指出的那样,除了以下划线开头的名称之外,您的问题似乎还存在于return *((PriorityPair<T>)(*queueArray[0]))._Object Let's look at this piece by piece, from the inside and working our way out. 让我们从内部逐个看一下,然后逐步解决。

queueArray is a PriorityPair<T>** , as declared in PriorityQueue . queueArrayPriorityPair<T>** ,如PriorityQueue所声明。 This could be read as “pointer to pointer to PriorityPair<T> ”, but in your case it looks like you mean it to be “raw array of pointer to PriorityPair<T> ”, which is also a valid reading. 这可以理解为“指向PriorityPair<T>指针的指针”,但是在您的情况下,您的意思是“指向PriorityPair<T>的指针的原始数组”,这也是一个有效的读数。 So far so good. 到现在为止还挺好。

queueArray[0] is a PriorityPair<T>*& , or “reference to pointer to PriorityPair<T> ”. queueArray[0]PriorityPair<T>*&或“对指向PriorityPair<T>指针的引用”。 References are pretty invisible in C++, and this just means you're dealing with the actual first element of the array and not a copy. 引用在C ++中几乎是看不见的,这仅意味着您正在处理数组的实际第一个元素,而不是副本。 Again, this is a reasonable thing to ask for when trying to peek at the top of the queue. 同样,当试图窥视队列的顶部时,这是一个合理的要求。

*queueArray[0] is simply a PriorityPair<T>& , or “reference to PriorityPair<T> ”. *queueArray[0]只是PriorityPair<T>&或“对PriorityPair<T>引用”。 Again, the reference here just means you're dealing with the actual thing being pointed to by queueArray[0] , and not a copy. 同样,这里的引用仅表示您正在处理的是queueArray[0]所指向的实际内容,而不是副本。

(PriorityPair<T>)(*queueArray[0]) is a PriorityPair<T> , the result of casting the one you already had to a new one. (PriorityPair<T>)(*queueArray[0])PriorityPair<T> ,将您已经拥有的那个转换为一个新的结果。 This creates a temporary PriorityPair<T> , which is the one you see destroyed later . 这将创建一个临时的PriorityPair<T> ,您稍后将看到它被销毁 There is no programmatic reason to do this cast (your IntelliSense issues are a different question and I don't know enough about VS to comment about them); 没有程序性的理由进行这种转换(您的IntelliSense问题是一个不同的问题,我对VS的了解不足以评论它们); it's already the right type. 它已经是正确的类型。 You can verify that it's a different one that's destroyed if you add this to the output, since this is a pointer to the current object and a temporary would need to live somewhere else in memory. 您可以验证这是一个不同的,如果你加入视讯聚会摧毁this到输出,因为this是一个指向当前对象和临时需要到别的地方居住在内存中。

((PriorityPair<T>)(*queueArray[0]))._Object is a T* , or “pointer to T ”. ((PriorityPair<T>)(*queueArray[0]))._ObjectT*或“ T指针”。 In fact, it points to the T that was stored for the top of the priority queue, which is good. 实际上,它指向优先级队列顶部存储的T ,这很好。

And finally, the full expression *((PriorityPair<T>)(*queueArray[0]))._Object dereferences this to give a T , and the return statement returns a copy of that T . 最后,完整表达式*((PriorityPair<T>)(*queueArray[0]))._Object取消引用以给出T ,并且return语句返回该T副本 This isn't affecting the behavior you're seeing, but if you added destructor calls to the objects you tested with, it would. 这不会影响您所看到的行为,但是如果您向测试对象添加了析构函数调用,它将会。 It would probably be more efficient to return a reference to the T , which would forgo the copying, by changing the return type from T to T& or T const& . 通过将返回类型从T更改为T&T const& ,返回对T的引用(将放弃复制)可能会更有效。

Other issues I noticed, not related to this question, which you might find useful when learning C++ (not a comprehensive list; I mostly wasn't looking for these): 我注意到的与该问题无关的其他问题,在学习C ++时可能会发现有用(不是完整的清单;我主要不是在寻找这些问题):

  • Both of your constructors should use initializer lists and have empty bodies (yes, new expressions can go in initializer lists, I think literally everybody I've spoken to about it either asked this the first time or assumed incorrectly, including me). 您的两个构造函数都应使用初始化程序列表,并且具有空主体(是的, new表达式可以放入初始化程序列表中,我认为与我交谈过的每个人实际上都是第一次问这个问题,或者包括我在内都假设不正确)。 This will somewhat more efficient and much more idiomatic. 这样会更有效,更惯用。
  • You don't need to implement a destructor for the PriorityPair (except for learning the nuances of the language); 您无需为PriorityPair实现析构函数(除了学习语言的细微差别外); it's what's known as a Plain Old Data (POD) type. 这就是所谓的普通旧数据(POD)类型。 If you wanted the PriorityPair 's destruction to delete the T s you would need that, but you want the T s to be managed completely separately. 如果您希望销毁PriorityPairdelete T ,则需要这样做,但是您希望对T进行完全分开的管理。
  • As people pointed out in the comments, you aren't allowed to use those identifier names yourself in case the compiler or standard library want them. 正如人们在评论中指出的那样,如果编译器或标准库需要它们,则您自己不能使用这些标识符名称 This might be fine, it might cause problems for you at compile time, or it might appear to work correctly but send all your users' browser history and emails to their parents and/or employers. 这样做可能很好, 可能会在编译时给您造成问题,或者可能会正常工作,但会将您所有用户的浏览器历史记录和电子邮件发送给他们的父母和/或雇主。 That last is unlikely, but the C++ standard doesn't forbid it; 最后的可能性不大,但是C ++标准并不禁止它。 this is what undefined behavior means. 这就是未定义行为的意思。 Other allowed behaviors are creating a black hole to destroy the Earth and shooting demons out of your nose , but these are even less likely in practice. 其他允许的行为是创建一个黑洞来摧毁地球,并从您的鼻子中射出恶魔 ,但实际上这种可能性更低。
  • I think like you have the destructor logic for PriorityQueue correct, and it's easy to mess that sort of thing up. 认为就像您对PriorityQueue正确的析构函数逻辑一样,很容易弄乱这种事情。 Congratulations! 恭喜!

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

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