【问题标题】:Overloading << operator on template classes在模板类上重载 << 运算符
【发布时间】:2012-02-07 11:01:12
【问题描述】:

我为链表中的节点创建了一个模板类,我试图通过重载

#include <iostream>
using namespace std;
template<class NType> class Node;

template<class NType>
class Node {
private:
        void deletePointer(NType* p);
public:
        NType data;
        Node *prev, *next;

        template<typename T>
        struct is_pointer { static const bool value = false; };

        template<typename T>
        struct is_pointer<T*> { static const bool value = true; };

        Node();
        Node(NType data);
        ~Node();
};  

int main() {
        Node<int> *n1 = new Node<int>();
        Node<int> *n2 = new Node<int>(10);

        std::cout << "Node 1: " << n1 << std::endl;
        std::cout << "Node 2: " << n2 << std::endl;
}

template<class NType> inline std::ostream & operator << (std::ostream& out, const Node<NType> &node){
        out << node.data;
        return out;
}

template<class NType> inline Node<NType>::Node()
            :data(NULL), prev(NULL), next(NULL)
{
}

template<class NType> inline Node<NType>::Node(NType data)
            :data(data), prev(NULL), next(NULL)
{
}

template<class NType> inline Node<NType>::~Node(){
        if(is_pointer<NType>::value){
                deletePointer(&data);
        } else {
                return;
        }
}

template<class NType> inline void Node<NType>::deletePointer(NType* p){
    delete p;
}

输出内存位置而不是节点内的数据。对于 int 之类的原始类型会发生这种情况,就好像它不知道 NType 容器中的数据类型一样。

Node 1: 0x741010
Node 2: 0x741030
Node 3: 0x741070
Node 4: 0x741090

我尝试使用typename 而不是class,但仍然没有骰子...有没有办法在插入之前动态找出模板使用的类型和转换或其他东西?我知道我可以为所有原语编写大量冗余代码,但这似乎既浪费又不必要。

如果有帮助,我将在 Arch Linux x64 上使用 GCC v4.6.2 20111223 进行编译

编辑:因为很多人都在提到它。我还尝试将类作为朋友和独立函数放在外面,这两者都不起作用,因为流输出地址而不是数据本身,无论我把它放在哪里。没有可访问的私有数据值,因此它可以不是朋友。

编辑: 测试用例:http://ideone.com/a99u5 上面还更新了源代码。

编辑: 添加了我的代码的其余部分,以帮助 Aaron 理解代码。

【问题讨论】:

  • 你的做法需要SomeNode &lt;&lt; cout,这显然不是你的意思。 ostream&amp; operator&lt;&lt; 通常应该始终是免费功能,因为您需要 LHS 上的 ostream。解决这个问题不一定能解决您的问题,但是...
  • 请制作一个测试用例。 ideone.com

标签: c++ templates operator-overloading


【解决方案1】:

您的代码将operator&lt;&lt; 声明为成员函数,因此它实际上将this 指针作为第一个参数,ostream 作为第二个参数。相反,它需要是一个免费的功能:

template<class NType> class Node {
public:
    NType data;
    Node *prev, *next;
};
//Note how this is declared outside of the class body, so it is a free function instead of a memberfunction
template<class NType> inline std::ostream& operator<<(std::ostream& out, const Node<NType>& val){
    out << val.data;
    return out;
}

但是,如果您的 operator&lt;&lt; 需要访问私有数据,您需要将其声明为友元函数:

template<class NType> class Node {
public:
    NType data;
    Node *prev, *next;
    friend std::ostream& operator<<(std::ostream& out, const Node& val){
        out << val.data;
        return out;
    }
};

现在输出:如果您的operator&lt;&lt; 被调用,编译器将知道NType 的类型,并在流式传输data 成员时做正确的事情。但是,由于您的 operator&lt;&lt; 不应该工作(如所写)并且它似乎给您 memoryaddresses 作为输出,我假设您有以下内容:

Node* n = new Node();
std::cout<<n;
//When it should be:
std::cout<<*n;

现在只是出于好奇:您为什么要自己实现看起来很像链表的东西,而不是简单地使用std::list

编辑: 现在我们可以看到测试用例,似乎关于如何调用operator&lt;&lt; 的假设是正确的。输出需要改成:

std::cout << "Node 1: " << *n1 << std::endl;
std::cout << "Node 2: " << *n2 << std::endl;

Node 实际调用operator&lt;&lt;,而不是为T* 调用通用函数

【讨论】:

  • 我知道在互联网和标准库中有多个链接列表的演绎版,但我只是想把它放在这里,以便我可以控制它的功能,以便我个人理解我自己的数据结构。
  • ...哇,真是个愚蠢的错误...我觉得自己像个新手哈哈。非常感谢。
  • @TechWiz:不要抱有这种感觉,它很快就会再次回到你的身边。 ;)
【解决方案2】:

为什么要在已经模板化的类方法前面添加template&lt;class NType&gt;

一般来说,重载操作符

template<typename NType>
friend std::ostream& operator<<(std::ostream& out, const Node<NType>& node)
{
    out << node.data;
    return out; 
}

编辑:为了回应下面的评论,我的意思是:

在标题中定义类模板时,您不会为类的成员函数重新声明该模板:

template<typename T>
class Foo
{
    T some_data;

    void member_fn();
}

但是,当您声明非会员朋友函数时,这是必需的:

template<typename NType>
class Node 
{
public:

    NType data;
    Node<NType> *prev, *next;

    //Note the different template parameter!
    template<typename NType1>
    friend std::ostream& operator<<(std::ostream& out, const Node<NType1>& node);
};

然后它的实现就变成了上面的template&lt;typename NType&gt; std::ostream&amp; operator&lt;&lt;(std::ostream&amp; out, const Node&lt;NType&gt;&amp; node) 实现。

【讨论】:

  • 我不关注。你还把template&lt;typename NType&gt;放在你的班级前面?这也返回地址。我在这里错过了什么吗?
  • 抱歉,我的复制和粘贴有点草率。该函数在类定义中声明并在类外部定义。但为了简洁起见,我把它移到了课堂上。这显然是一个愚蠢的想法,因为它造成了看起来应该有的更多混乱。
【解决方案3】:
template <class NType>
class Node
{
  public:
    NType data;
    Node *prev, *next;
};

template <class NType>
inline std::ostream& operator<<(std::ostream& os, const Node<NType>& n)
{
    return out << n.data;
}

【讨论】:

  • 我之前已经尝试过(为了以防万一你再次提到它),但同样的交易。
【解决方案4】:

您打印的是节点地址,而不是节点:

int main() {
        Node<int> *n1 = new Node<int>();
        Node<int> *n2 = new Node<int>(10);

        std::cout << "Node 1: " << n1 << std::endl;
        std::cout << "Node 2: " << n2 << std::endl;
}

n1n2指针,而不是对象。你应该写:

int main() {
        Node<int> *n1 = new Node<int>();
        Node<int> *n2 = new Node<int>(10);

        std::cout << "Node 1: " << *n1 << std::endl;
        std::cout << "Node 2: " << *n2 << std::endl;
}

或:

int main() {
        Node<int> n1();
        Node<int> n2(10);

        std::cout << "Node 1: " << n1 << std::endl;
        std::cout << "Node 2: " << n2 << std::endl;
}

【讨论】:

  • 是的,这就是问题所在,谢谢。该死的 Java 试图给我灌输糟糕的习惯。
【解决方案5】:

其他人指出您需要使用cout &lt;&lt; *n1 而不是cout &lt;&lt; n1,但是您的代码中还有另一个重要的错误。

您的析构函数包含一个调用deletePointer(&amp;data);,其中data 是该类的成员。这是危险的。如果这行代码曾经被执行过,那么程序很可能会崩溃。 data 只是this 指向的对象的一小部分,试图删除它就像试图删除数组中的单个元素是int。

Node<int> *n1 = new Node<int>();
delete n1; // this makes sense. deleting the same thing that was new'ed

Node<int> *n1 = new Node<int>();
delete &(n1->data); // BUG

您可能应该对您的设计进行大量更改,并简单地删除指针的特殊代码。您是否希望编写如下代码:Node&lt;int*&gt; *n2 = new Node&lt;int*&gt;( new int );?如果是这样,您希望它如何表现?

如果NType 实际上是指针类型,比如int*,那么deletePointer(data) 可能有意义,但deletePointer(&amp;data) 永远没有意义。

【讨论】:

  • 析构函数在调用deletePointer之前会检查指针数据,所以如果data不是指针,析构函数不会调用函数。还告诉 delete 从它自己的解构代码之外删除一个成员变量似乎应该是错误的,无论它是否是正确构造的代码。这似乎是糟糕的编程。解构器也不应该被直接调用,而是应该在程序关闭时成为垃圾收集的一部分,并且这些节点是更大的东西的一部分,所以它甚至不应该以这种方式使用。
  • @TechWiz,这不正确,抱歉。让我们清楚一点。即使data指针类型,也应该用delete data删除,而不是delete &amp;data
  • 还有@TechWiz,这是 C++,不是 Java。你说“......但应该是垃圾收集的一部分”。
  • is_pointer&lt;NType&gt;::value 当且仅当 X 是指针类型时才会为真。例如,NTypeint *,那么它是指针类型。另外,如果NTypeint *,那么data会被声明为int * data;,因此需要调用delete data来删除data指向的int。
  • 很抱歉你错了。它工作得很好,你自己试试。该程序既不会崩溃也不会造成内存泄漏。我知道这不是java,但默认情况下,只要定义了一个,就会在程序执行完毕时对所有非指针值调用解构函数。请更仔细地查看代码,静态struct is_pointer 在非指针类型上总是为假,因为NType 不会是指针,除非它当然是指针类型。我的帖子中还缺少deletePointer() 的代码,但我很乐意编辑我的问题以包含它。
猜你喜欢
  • 2011-04-30
  • 1970-01-01
  • 1970-01-01
  • 2016-10-15
  • 2013-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多