【问题标题】:Reference to a derived template class object incorrectly passed to overloaded operator<< body对派生模板类对象的引用错误地传递给重载的 operator<< body
【发布时间】:2021-03-15 13:45:53
【问题描述】:

我使用 Visual Studio 2019 编译器。出于教育目的,我决定实现/创建一个基于模板的 FIFO 队列。为了实现这一点,我决定继承早期实现且运行良好的双向链表(也基于模板)。 下面是我为清晰呈现而特意简化的代码(我删除了所有无关紧要的成员函数声明和定义):

Doubly_linked_list.h:

#pragma once

#include <iostream>
#include <string.h>
using namespace std;

template <class T>
class list;

template <class T>
ostream& operator<<(ostream& out, const list<T>& arg);

template <class T>
class list
{
protected:
    struct node
    {
        T* obj_ptr;
        node* next_node_ptr;
        node* previous_node_ptr;
        node()
            : next_node_ptr(NULL), previous_node_ptr(NULL), obj_ptr(NULL) {}
    };

    node* first_node_ptr;
    node* last_node_ptr;
    node* chosen_node_ptr;

    int element_counter;

public:
    list()
    {
        first_node_ptr = last_node_ptr = chosen_node_ptr = NULL;
        element_counter = 0;
    }

    friend ostream& operator<< <T>(ostream& out, const list<T>& arg);

    void set_chosen_after_last()
    {
        chosen_node_ptr = NULL;
    }

    void add_element(T& obj);

    ~list();

private:
    void add_element_as_first(node* newly_added_node_ptr);
    void add_element_as_last(node* newly_added_node_ptr);
    void add_element_in_middle(node* newly_added_node_ptr);
};
//##################################################################################
//##################################################################################


template <class T>
list<T>::~list()
{
    if (!first_node_ptr) 
        return;

    node* bishop;
    int i;
    cout << "\n\n";

    for (chosen_node_ptr = first_node_ptr, i = 0; chosen_node_ptr; i++)
    {
        bishop = chosen_node_ptr->next_node_ptr;
        delete chosen_node_ptr;
        chosen_node_ptr = bishop;
    }
}


template <class T>
ostream& operator<<(ostream& out, const list<T>& arg)
{
    if (!(arg.first_node_ptr))
        return out;

    typename list<T>::node* chaser = arg.first_node_ptr;

    for (; chaser; chaser = chaser->next_node_ptr)
    {
        out << *(chaser->obj_ptr) << "\t";
    }

    return out;
}


template <class T>
void list<T>::add_element_in_middle(node* newly_added_node_ptr)
{
    node* previous_node = chosen_node_ptr->previous_node_ptr;
    previous_node->next_node_ptr = newly_added_node_ptr;
    newly_added_node_ptr->previous_node_ptr = previous_node;
    newly_added_node_ptr->next_node_ptr = chosen_node_ptr;
    chosen_node_ptr->previous_node_ptr = newly_added_node_ptr;
    chosen_node_ptr = newly_added_node_ptr;
}


template <class T>
void list<T>::add_element_as_last(node* newly_added_node_ptr)
{
    last_node_ptr->next_node_ptr = newly_added_node_ptr;
    newly_added_node_ptr->previous_node_ptr = last_node_ptr;
    last_node_ptr = newly_added_node_ptr;
}



template <class T>
void list<T>::add_element_as_first(node* newly_added_node_ptr)
{
    if (!first_node_ptr)   
    {
        first_node_ptr = newly_added_node_ptr;
        last_node_ptr = newly_added_node_ptr;
    }
    else
    {
        newly_added_node_ptr->next_node_ptr = first_node_ptr;
        first_node_ptr->previous_node_ptr = newly_added_node_ptr;
        first_node_ptr = newly_added_node_ptr;
        chosen_node_ptr = first_node_ptr;
    }
}

template <class T>
void list<T>::add_element(T& obj)
{
    node* new_node_ptr = new node;
    new_node_ptr->obj_ptr = &obj;

    if (!first_node_ptr || chosen_node_ptr == first_node_ptr)
        add_element_as_first(new_node_ptr);
    else
    {
        if (!chosen_node_ptr)
        {
            add_element_as_last(new_node_ptr);
        }
        else
        {
            add_element_in_middle(new_node_ptr);
        }
    }

    element_counter++;
}

队列.h:

#pragma once
#include "Doubly_linked_list.h"

template <class t>
class queue;

template <class T>
ostream& operator<<(ostream& out, typename const queue<T>& arg);

template <class T>
class queue : private list<T>
{
public:
    void push(T arg)
    {
        list<T>::set_chosen_after_last();
        list<T>::add_element(arg);
    }

    friend ostream& operator<< <> (ostream& out, typename const queue<T>& arg);
};


template <class T>
ostream& operator<<(ostream& out, typename const queue<T>& arg)
{
    if (!(arg.list<T>::first_node_ptr))
        return out;

    typename list<T>::node* chaser = arg.first_node_ptr;

    for (; chaser; chaser = chaser->next_node_ptr)
    {
        out << *(chaser->obj_ptr) << "\t";
    }

    return out;
}

Main.cpp:

#include <iostream>
#include "Queue.h"


int main()
{
    queue<int> ex1;
    ex1.push(5);

    cout << ex1;
}

作为测试上述代码的结果,我希望在控制台上打印数字 5,但结果是一些随机整数(每次运行此代码时都不同),如下所示:

7599868

我开始调试我的代码并注意到 arg 是重载运算符

Debugger_mode_screen_shot.png

我现在对这种行为的可能根本原因没有任何想法。据我所知,arg 应该指代内存中与 ex1 完全相同的对象,因此它们的 obj_ptr 指针应该指向完全相同的 T 类型(在本例中为整数)对象和完全相同的值(在本例中为 5)。虽然这不是实际发生的事情。我在这里阅读了许多关于 SO 的主题,但找不到任何可以解决这种特殊情况/问题的主题。

更有趣并让我更加困惑的是,我试图通过编写一个单独的简单代码来模仿原始代码中使用的继承机制来重现这个问题。然而,这里的结果完全符合预期 - 对继承的模板类对象的引用正确地传递给重载运算符(ptr_a 指针指向的整数对象的正确值),因此控制台最终打印出正确的值:

#include <iostream>
#include <string.h>

using namespace std;

template <class T>
class Base;

template <class T>
ostream& operator<<(ostream& out, Base<T>& arg);


template <class T>
class Base
{
protected:
    struct node
    {
        T* ptr_a;
        node* next_node_ptr;

        node()
        {
            ptr_a = new T;
            next_node_ptr = NULL;
        }
    };

    node* first_node_ptr;

public:
    Base()
    {
        first_node_ptr = new node;
        *(first_node_ptr->ptr_a) = 5;
    }

    friend ostream& operator<< <> (ostream& out, Base<T>& arg);
};


template <class t>
ostream& operator<<(ostream& out, Base<t>& arg)
{
    cout << "this is " << *arg.first_node_ptr->ptr_a << endl;
    return out;
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

template <class T>
class Deriv;

template <class T>
ostream& operator<<(ostream& out, const Deriv<T>& arg);

template <class T>
class Deriv : private Base<T>
{
public:
    void new_function() {}

    friend ostream& operator<< <> (ostream& out, const Deriv& arg);
};


template <class T>
ostream& operator<<(ostream& out, const Deriv<T>& arg)
{
    cout << "This is " << *arg.first_node_ptr->ptr_a << endl;
    return out;
}


int main()
{
    Base<int> ex1;
    cout << ex1;

    Deriv<int> ex2;
    cout << ex2;
}

输出:

this is 5
This is 5

值得一提的是,重载运算符工作得很好。我还尝试使用 Code::Blocks 来测试此代码,但问题也仍然存在。正如你所看到的,我通过在实现实际的模板类定义之前首先向前声明它来与 operator 和所有以前的类似代码中对我来说效果很好。

你们中的任何人都可以指出这种情况的可能根本原因吗?我现在应该做什么才能使其按预期工作(在这种情况下打印 5 而不是一些随机数)?提前感谢您的任何想法。

【问题讨论】:

  • 您的代码存储了一个指向对象的指针,该对象随后被销毁。当您取消引用此指针时,这是未定义的行为,并解释了您看到的垃圾值。

标签: c++ templates inheritance operator-overloading


【解决方案1】:

这段代码存储了一个指向obj引用的对象的指针。

template <class T>
void list<T>::add_element(T& obj)
{
    node* new_node_ptr = new node;
    new_node_ptr->obj_ptr = &obj;
    ...

此代码调用list&lt;T&gt;::add_element 并引用一个本地对象,即arg

void push(T arg)
{
    list<T>::set_chosen_after_last();
    list<T>::add_element(arg);
}

一旦push 退出add_element 存储的指针无效。这解释了您以后使用指针时看到的垃圾值。

解决方案不是在节点中存储指针,而是将传递给列表和队列函数的对象复制或移动到节点。

【讨论】:

  • 非常感谢@john!我有一种感觉,我的代码中仍然缺少一些简单但“不可见”的细节......这就是我通常不会犯的错误...... :) 我仍然在看错误的部分我的代码正在寻找错误...真丢脸 :) 但仍然吸取了教训。
  • 我现在已将推送函数原型从 void push(T arg) 更改为 void push(T& arg) 并在主函数中,如push 函数参数,我使用实际的整数对象/变量而不是我最初使用的右值引用:` int a = 5; ex1.push(a); ` 现在一切正常。再次感谢您!
  • 另外,我现在太“年轻”了,无法以额外积分的形式给你适当的信用(我昨天在这里创建了一个帐户),所以我不允许我在时刻。
猜你喜欢
  • 2017-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-28
  • 1970-01-01
  • 2010-12-12
相关资源
最近更新 更多