【问题标题】:overloading friend operator<< for template class为模板类重载友元运算符<<
【发布时间】:2011-06-07 07:17:15
【问题描述】:

我现在已经在 StackOverflow.com 上阅读了几个关于我的问题的问题,但似乎都没有解决我的问题。或者我可能做错了...... 如果我将重载的&lt;&lt; 做成内联函数,它就可以工作。但是我该如何让它在我的情况下发挥作用呢?

warning: friend declaration std::ostream&amp; operator&lt;&lt;(std::ostream&amp;, const D&lt;classT&gt;&amp;)' declares a non-template function

warning: (if this is not what you intended, make sure the function template has already been declared and add &lt;&gt; after the function name here) -Wno-non-template-friend disables this warning

/tmp/cc6VTWdv.o:uppgift4.cc:(.text+0x180): undefined reference to operator<<(std::basic_ostream<char, std::char_traits<char> >&, D<int> const&)' collect2: ld returned 1 exit status

代码:

template <class T>
T my_max(T a, T b)
{
   if(a > b)      
      return a;
   else
      return b;
}

template <class classT>
class D
{
public:
   D(classT in)
      : d(in) {};
   bool operator>(const D& rhs) const;
   classT operator=(const D<classT>& rhs);

   friend ostream& operator<< (ostream & os, const D<classT>& rhs);
private:
   classT d;
};


int main()
{

   int i1 = 1;
   int i2 = 2;
   D<int> d1(i1);
   D<int> d2(i2);

   cout << my_max(d1,d2) << endl;
   return 0;
}

template <class classT>
ostream& operator<<(ostream &os, const D<classT>& rhs)
{
   os << rhs.d;
   return os;
}

【问题讨论】:

  • 最近有一个关于这个的问题可能很有启发性:stackoverflow.com/questions/4571611/virtual-operator
  • @Daniel - 它不会解决我在为模板类重载时遇到的问题
  • 我认为最好不要用给定的答案修改问题。这使得更难确定最初的问题是什么。您可能希望在末尾添加 EDIT 并添加 解决 问题的更改,但是当问题超时更改时我发现它很混乱,我必须将历史记录拉到首先看看实际被问到了什么。

标签: c++ templates operator-overloading friend ostream


【解决方案1】:

这是常见问题之一,它们具有相似但不完全相同的不同方法。这三种方法的不同之处在于你声明谁是你的函数的朋友——以及你如何实现它。

性格外向的人

将模板的所有实例声明为好友。这是您接受的答案,也是大多数其他答案的建议。在这种方法中,您通过声明所有operator&lt;&lt; 实例化的朋友来不必要地打开您的特定实例化D&lt;T&gt;。也就是说,std::ostream&amp; operator&lt;&lt;( std::ostream &amp;, const D&lt;int&gt;&amp; ) 可以访问D&lt;double&gt; 的所有内部。

template <typename T>
class Test {
   template <typename U>      // all instantiations of this template are my friends
   friend std::ostream& operator<<( std::ostream&, const Test<U>& );
};
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& ) {
   // Can access all Test<int>, Test<double>... regardless of what T is
}

内向者

仅将插入运算符的特定实例声明为友元。 D&lt;int&gt; 可能喜欢将插入运算符应用于自身,但它不希望与 std::ostream&amp; operator&lt;&lt;( std::ostream&amp;, const D&lt;double&gt;&amp; ) 有任何关系。

这可以通过两种方式完成,最简单的方式是 @Emery Berger 提出的,即内联操作符——出于其他原因,这也是一个好主意:

template <typename T>
class Test {
   friend std::ostream& operator<<( std::ostream& o, const Test& t ) {
      // can access the enclosing Test. If T is int, it cannot access Test<double>
   }
};

在第一个版本中,您不是创建一个模板化的operator&lt;&lt;,而是为Test 模板的每个实例创建一个非模板化函数。同样,差异是微妙的,但这基本上相当于手动添加:std::ostream&amp; operator&lt;&lt;( std::ostream&amp;, const Test&lt;int&gt;&amp; ),当您实例化 Test&lt;int&gt; 时,以及另一个类似的重载,当您使用 double 或任何其他类型实例化 Test 时。

第三个版本比较麻烦。无需内联代码,并使用模板,您可以将模板的单个实例声明为您的类的朋友,而无需向所有其他实例开放自己:

// Forward declare both templates:
template <typename T> class Test;
template <typename T> std::ostream& operator<<( std::ostream&, const Test<T>& );

// Declare the actual templates:
template <typename T>
class Test {
   friend std::ostream& operator<< <T>( std::ostream&, const Test<T>& );
};
// Implement the operator
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& t ) {
   // Can only access Test<T> for the same T as is instantiating, that is:
   // if T is int, this template cannot access Test<double>, Test<char> ...
}

利用外向的人

第三个选项和第一个选项之间的细微差别在于您对其他课程的开放程度。 外向版本中的一个滥用示例是有人想要访问您的内部并这样做:

namespace hacker {
   struct unique {}; // Create a new unique type to avoid breaking ODR
   template <> 
   std::ostream& operator<< <unique>( std::ostream&, const Test<unique>& )
   {
      // if Test<T> is an extrovert, I can access and modify *any* Test<T>!!!
      // if Test<T> is an introvert, then I can only mess up with Test<unique> 
      // which is just not so much fun...
   }
}

【讨论】:

  • 在类之外实现函数会给出未定义的引用。在类内部,一旦我明确地将宿主类实例化为多个类型,就会出现问题。
  • @dgrat:我无法调试我没有看到的代码,但它确实有效。朋友是否依赖于任何模板参数?如果不是,您会遇到多个定义的问题,因为不同的实例化会生成完全相同的 same 函数(如果函数签名相同但主体不同,则会违反 ODR)。
  • 这仍然是我更多地推荐给那些寻求朋友模板函数(不仅仅是运算符)的人之一,这在很大程度上是因为我可以完成的方式存在明显差异,并且每个人的好处/缺点。只是一个出色的答案,大卫。如果可以的话,我会把它提高十几次。
  • 在第三版中是“operator” 这行表示将operator
  • @wangdq 是的,并且该专业化与该模板专业化为友,并且与该模板专业化为友。 IE。如果你有一些 Test&lt;T&gt; 唯一的 operator &lt;&lt;operator &lt;&lt; &lt;T&gt; 实例化。一些随意的Test&lt;U&gt;,其中U不是T的同义词,不是朋友operator &lt;&lt; &lt;T&gt;;而是它的朋友operator &lt;&lt; &lt;U&gt;。它可能看起来没什么大不了,但它可以在不知不觉中悄悄地成为一个。
【解决方案2】:

你不能这样声明一个朋友,你需要为它指定一个不同的模板类型。

template <typename SclassT>
friend ostream& operator<< (ostream & os, const D<SclassT>& rhs);

注意SclassT,这样它就不会影响classT。定义时

template <typename SclassT>
ostream& operator<< (ostream & os, const D<SclassT>& rhs)
{
  // body..
}

【讨论】:

  • 感谢这个作品用这段代码编辑了我的问题,一旦代码下降,我将把它作为答案。
  • 请注意,这并不是将operator&lt;&lt; 的特定实例化声明为友元,而是将所有 实例化,包括模板的任何特化。见答案here
  • @starcorn 您应该更改您选择的答案以提供更好的答案,这应该是大卫罗德里格斯的答案。
  • @Nim 根据用户意见我更改了我的问题的选定答案。但是,感谢您当时为帮助我所做的贡献:)
  • @starcorn,不用担心,我当时也投票支持大卫的答案,这是对问题的更全面的处理..
【解决方案3】:

这对我有用,没有任何编译器警告。

#include <iostream>
using namespace std;

template <class T>
T my_max(T a, T b)
{
  if(a > b)
    return a;
  else
    return b;
}

template <class classT>
class D
{
public:
  D(classT in)
    : d(in) {};

  bool operator>(const D& rhs) const {
    return (d > rhs.d);
  }

  classT operator=(const D<classT>& rhs);

  friend ostream& operator<< (ostream & os, const D& rhs) {
    os << rhs.d;
    return os;
  }

private:
  classT d;
};


int main()
{

  int i1 = 1;
  int i2 = 2;
  D<int> d1(i1);
  D<int> d2(i2);

  cout << my_max(d1,d2) << endl;
  return 0;
}

【讨论】:

  • 是的,我已经这样做了,但是如果我不想将 operator&lt;&lt; 作为内联函数呢?
  • @starcorn:方法/函数是否被标记为内联(隐式或显式)与函数实际上内联在代码中几乎没有关系。因此,这是一个毫无意义的担心。
  • +1。 @starcorn:此解决方案比公认的解决方案更好。差异是微妙的,但在接受的答案中,您将operator&lt;&lt; 的所有实例化(和可能的特化)声明为朋友,而在此解决方案中,您仅授予对具有相同类型的operator&lt;&lt; 实例化的访问权限.此外,作为在类中定义operator&lt;&lt; 的副作用,您将operator&lt;&lt; 的可见性限制为仅在两个参数之一是D 的情况下——编译器甚至不会考虑@987654329 @ 重载,除非一个参数是D&lt;T&gt;
  • @starcorn:我添加了一个答案,试图消除三种不同方法的差异here
【解决方案4】:

我认为你一开始就不应该交朋友。

您可以创建一个公共方法调用 print,类似这样(对于非模板类):

std::ostream& MyClass::print(std::ostream& os) const
{
  os << "Private One" << privateOne_ << endl;
  os << "Private Two" << privateTwo_ << endl;
  os.flush();
  return os;
}

然后,在类之外(但在同一个命名空间中)

std::ostream& operator<<(std::ostream& os, const MyClass& myClass)
{
  return myClass.print(os);
}

我认为它也应该适用于模板类,但我还没有测试过。

【讨论】:

    【解决方案5】:

    给你:

    #include <cstdlib>
    #include <iostream>
    using namespace std;
    
    template <class T>
    T my_max(T a, T b)
    {
       if(a > b)      
          return a;
       else
          return b;
    }
    
    template <class classT>
    class D
    {
    public:
       D(classT in)
          : d(in) {};
       bool operator>(const D& rhs) const { return d > rhs.d;};
       classT operator=(const D<classT>& rhs);
    
       template<class classT> friend ostream& operator<< (ostream & os, const D<classT>& rhs);
    private:
       classT d;
    };
    
    template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs)
    {
        os << rhs.d;
        return os;
    }
    
    
    int main()
    {
    
       int i1 = 1;
       int i2 = 2;
       D<int> d1(i1);
       D<int> d2(i2);
    
       cout << my_max(d1,d2) << endl;
       return 0;
    }
    

    【讨论】:

    • 我认为这不应该编译:template&lt;class classT&gt; ostream&amp; operator&lt;&lt;(ostream&amp; os, class D&lt;typename classT&gt; const&amp; rhs)。参数声明中不允许使用详细类型,typename 需要qualified-id。
    • @Gene:嗯。它确实在 MS 扩展关闭的情况下为我编译了最高级别。
    • 它不能用 g++ 编译,我相信这个编译器。 operator&lt;&lt; 中的第二个参数是class D&lt;typename classT&gt;,我认为这是不正确的。我会改用D&lt;classT&gt;。关键字class 在那里是可选的(99.9% 的情况),但typename 的使用不是两个已知用途之一:它作为class 的替代品是无效的,它是为了识别模板上的从属名称实际上是一种类型。
    猜你喜欢
    • 2021-05-18
    • 2010-11-20
    • 2011-05-08
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多