【问题标题】:Friendship problems when overriding operator<<覆盖运算符时的友谊问题<<
【发布时间】:2011-12-22 21:54:38
【问题描述】:

我正在尝试以标准方式重载 operator&lt;&lt;。我有一个名为 SymbolTable 的类,它位于一个名为 SymbolTable.h 的文件中,如下所示:

namespace Compaler // It's a pun; don't ask
{

class SymbolTable
{
public:
  // ...
  friend ostream& operator<<(ostream &out, const SymbolTable &table);
private:
  vector<map<int, Symbol*> > mSymbols;
};

// ...

}

operator&lt;&lt; 的实现访问了 SymbolTable 的一些私有成员。这是实现的签名,以证明它与前向声明的签名没有区别(我复制粘贴它以确保我不会发疯。)

ostream& operator<<(ostream &out, const SymbolTable &table)
{ ... }

如果我将实现放在第二个 ... 所在的 SymbolTable.h 中,我会收到以下链接器错误:

ld: 重复符号 Compaler::operator&, Compaler::SymbolTable const&)in /var/folders/kt/pl6qd1490fn3yqxfpg64jyr80000gn/T//ccstrYnU.o 和 /var/folders/kt /pl6qd1490fn3yqxfpg64jyr80000gn/T//ccDQFiyK.o 用于架构 x86_64

但是,如果我把它放在 SymbolTable.cpp 中,我的 SymbolTable 成员实现的其余部分都在其中,代码甚至无法编译;我收到与访问私人成员相关的错误,表明友谊未被识别:

../SymbolTable/SymbolTable.h:在函数'std::ostream& operator, Compaler::Symbol*, std::less, std::allocator >>, std::allocator, std: :allocator >, Compaler::Symbol*> > >, std::allocator, std::allocator >, Compaler::Symbol*, std::less, std::allocator >>, std::allocator, std::分配器 >, Compaler::Symbol*> > > > > Compaler::SymbolTable::mSymbols' 是私有的 ../SymbolTable/SymbolTable.cpp:98:错误:在此上下文中

我做错了什么?

答案总结:

感谢 Seth Carnegie 和 Daniel R. Hicks,我已经弄明白了。为了将来可能犯同样错误的人的利益,我将在我的问题中总结答案,因为在 cmets 中有很多来回。

第一个(在 SymbolTable.h 中实现时出现重复符号)问题发生是因为我试图同时编译和链接两个包含 SymbolTable.h 的文件。我一直在误解#ifdef 包括警卫的工作方式;也就是说,它们只会阻止代码在一个文件中包含两次,但如果您尝试链接两个都包含来自第三个文件的代码的文件,它们将无济于事。

第二个问题是当我将 operator&lt;&lt; 实现放在 SymbolTable.cpp 文件中时,我忘记将它放在 Compaler 命名空间中。

【问题讨论】:

  • inline添加到头文件中的函数定义中,这应该会使编译器对多重定义闭嘴。
  • 您确定命名空间正确吗?作为一种解决方法,您可以将函数定义与友谊线一起放在类定义中......但这不是答案。
  • 在第一种情况下,您会收到重复符号错误,因为 SymbolTable.H 包含在多个 .C 文件中。
  • 在我看来你已经将它包含在 SymbolTable.cpp 和 SymbolTableTest.cpp 中。
  • 愚蠢的问题:当您将operator &lt;&lt; 定义放在.CPP 文件中时,您是否使用SymbolTable 适当地指定命名空间?如果您不以某种方式提供命名空间,SymbolTable 的出现似乎与 .H 文件中的完全不同。

标签: c++ linker operator-overloading friend access-control


【解决方案1】:

如果在头文件中做需要使用内联:

namespace Compaler // It's a pun; don't ask
{    
    class SymbolTable
    {
        public:
            friend ostream& operator<<(ostream &out, const SymbolTable &table);
        private:
            vector<map<int, Symbol*> > mSymbols;
    };

    inline ostream& operator<<(ostream &out, const SymbolTable &table)
    {
         out << table[0].something;
         return out;
    }
}

如果你想在源文件中做。那么操作符

头文件.h

namespace Compaler // It's a pun; don't ask
{    
    class SymbolTable
    {
        public:
            friend ostream& operator<<(ostream &out, const SymbolTable &table);
        private:
            vector<map<int, Symbol*> > mSymbols;
    };
    ostream& operator<<(ostream &out, const SymbolTable &table);
}

Source.cpp

#include "Header.h"
namespace Compaler   // must be here
{    
    ostream& operator<<(ostream &out, const SymbolTable &table)
    {
         out << table[0].something;
         return out;
    }
}

【讨论】:

    【解决方案2】:

    第一个问题是因为您将定义放在头文件中并且#includeing 两次。这会导致多重定义错误。

    至于第二个问题,可能您的函数与friend 声明的签名不同。

    只是让您知道,作为获得正确签名的替代方法,您可以内联编写函数:

    class SymbolTable
    {
    public:
      // ...
      friend ostream& operator<<(ostream &out, const SymbolTable &table) {
          ...
      }
    private:
      vector<map<int, Symbol*> > mSymbols;
    };
    

    编辑:由于显然签名是正确的,错误就在其他地方。您必须发布更多代码。

    【讨论】:

    • 头文件有#ifdef保护以防止双重包含,无论如何,我100%肯定它只被包含一次,因为我现在只是想编译一个简单的单元测试.我猜你在我编辑问题之前就开始输入你的答案了……我包括了实现的签名;我从前向声明中复制粘贴它并反复检查以确保它是相同的。
    • @MitchLindgren #ifdef 守卫在这种情况下没有帮助;那些只会阻止文件被多次包含在同一个文件中,而不是单独的文件中。例如,如果你在 a.cpp 中包含两次,#ifdef 守卫会起作用,但如果你在 a.cppb.cpp 中都包含一次,#ifdefs 不会有什么不同你会得到一个链接器错误。
    • @MitchLindgren 也很明显,当您收到有关访问私人成员的错误时,您的代码不在 SymbolTable.cpp 中,因为错误显示为 SymbolTable.h
    • 哎呀,你是对的。我现在看到导致重复符号问题的原因。我的错。关于访问私有成员的错误,我错过了输出中的一行。我已经编辑了我的问题以包含它。
    • 请注意,#ifdefs 不会阻止将 .H 包含在两个不同的 .CPP 文件中。
    【解决方案3】:

    正如其他答案和 cmets 指出的那样,可以将其作为朋友功能来执行。但是,我经常喜欢的另一种方法是创建一个公共打印函数,它将对象打印到 std::ostream,然后让 operator

    namespace Compaler // It's a pun; don't ask
    {
    
    class SymbolTable
    {
    public:
      // ...
      void print(ostream & out); // basically your definition of operator<<, in your .cpp file
    private:
      vector<map<int, Symbol*> > mSymbols;
    };
    
    // this doesn't have to be inline, but since it's a two-liner, there's probably no harm either
    inline ostream& operator<<(ostream &out, const SymbolTable &table)
    {
        table.print(out);
        return out;
    }
    
    // ...
    
    }
    

    这也让您的客户可以选择避免使用

    另一个技巧:如果对多个类执行此操作,则可以将 operator

    template <typename T>
    inline ostream& operator<<(ostream &out, const T &obj)
    {
        obj.print(out);
        return out;
    }
    

    由于这是一个模板函数,任何对它的特定定义都将覆盖这个定义,并且没有定义 print(ostream&) 的类只要它们从不输出到流中就可以了,因为这个函数在这种情况下永远不会被实例化。

    【讨论】:

    • 结交朋友不会破坏封装。它增加了封装并记录了对象的紧密结合。见programersstackoverflow
    • 虽然我对 print() 方法没有任何问题(我有时会这样做)。 clients 的选项可以避免 就像是说 C 用户可以避免指针。理解流操作符是 C++ 的基础,这使得 STL 库非常有用(参见 std::istream_iterator)。
    猜你喜欢
    • 2010-12-22
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多