【问题标题】:operator overloading c++ / where to put my code?运算符重载 C++ / 将我的代码放在哪里?
【发布时间】:2011-03-27 14:51:14
【问题描述】:

今天我在我的一个类中重载了

#ifndef TERMINALLOG_HH
#define TERMINALLOG_HH

using namespace std;

class Terminallog {
public:

    Terminallog();
    Terminallog(int);
    virtual ~Terminallog();

    template <class T>
    Terminallog &operator<<(const T &v);

private:

};

#endif

如您所见,我在头文件中定义了重载运算符,然后在我的 .cc 文件中继续实现它:

//stripped code

template <class T>
Terminallog &Terminallog::operator<<(const T &v) {
    cout << endl;
    this->indent();
    cout << v;
    return *this;
}

//stripped code

之后我使用我的新类创建了一个 main.cpp 文件:

#include "inc/terminallog.hh"

int main() {
    Terminallog clog(3);
    clog << "bla";
    clog << "bla";
    return 0;
}

然后我继续编译:

g++ src/terminallog.cc inc/terminallog.hh testmain.cpp -o test -Wall -Werror 
/tmp/cckCmxai.o: In function `main':
testmain.cpp:(.text+0x1ca): undefined reference to `Terminallog& Terminallog::operator<< <char [4]>(char const (&) [4])'
testmain.cpp:(.text+0x1de): undefined reference to `Terminallog& Terminallog::operator<< <char [4]>(char const (&) [4])'
collect2: ld returned 1 exit status

砰!一个愚蠢的链接器错误,我仍然不知道它来自哪里。我玩了一下,发现将重载运算符的实现放在头文件中可以解决所有问题。现在我比以前更困惑了。

为什么我不能将重载运算符的实现放在我的 .cc 文件中?为什么我把它放在我的头文件中运行平稳?

先谢谢了

ftiaronsem

【问题讨论】:

  • 链接器错误绝不是愚蠢的 - 它们与编译器错误一样提供信息。
  • @ftiaronsem:顺便说一句 - 不要将 using namespace std; 放在头文件中 - 您可以将其放在 'cc' 文件中,然后根据需要在头文件中使用 std:: 限定符。
  • @quamrana:感谢您的评论,我可以做到。但为什么它被认为是不好的做法?
  • @ftiaronsem:它在全局命名空间中包含 std 命名空间,用于该标头的所有客户端以及您之后包含的任何标头。例如,看到这个答案:stackoverflow.com/questions/1265039/using-std-namespace/…

标签: c++ operator-overloading


【解决方案1】:

编译器必须看到实现才能使用模板。通常这意味着你把它放在标题中。

【讨论】:

    【解决方案2】:

    可以将实现保留在 cpp 文件中,但是您需要为使用它的每种类型声明模板的用法。更详细的解释请见Parashift C++ Faq

    在您的情况下,您必须在 cpp 文件中的某处写下该行:

    template Terminallog &Terminallog::operator<<(const char* &v);
    

    【讨论】:

      【解决方案3】:

      除了@Bo 的回答:您还应该阅读 C++ FAQ Lite 中的文章:http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12 等。

      【讨论】:

        【解决方案4】:

        模板有一个特殊的属性:他们的“实现”可以被认为是他们签名的一部分。

        如果编译器在您的示例中只看到该行

        Terminallog &operator<<(const T &v);
        

        你可以使用 operator

        但是,您的运算符实现需要一种可以通过管道传输到 cout 的类型! 对于许多类型而言,情况并非如此!尝试将您的 Terminallog 类通过管道传输到 cout。 那是行不通的。编译器只能检查这种合规性, 如果你告诉它你想用 T 做什么。

        第二部分: 了解#include 的作用。 C/C++(可悲)不知道接口和实现之间的真正区别,它没有模块系统。标头、cpps 都只是一个约定。你可以愉快地写 #include &lt;something.cpp&gt;。没有什么能阻止你。 #include"inc/terminallog.hh" 语句只是将头文件中的所有内容转储到当前文件中(以及那里的所有内容#included)。这是我们使用包含守卫 a'la #ifndef TERMINALLOG_HH 的一个稍微令人沮丧的原因,因为像字符串或类似的头文件可能会被转储到我们的文件中一百次或更多次,并且编译器会不断抛出重新定义错误。 实际上,如果您添加了 `#include,它很可能会删除您的错误,因为 Terminallog 的实现现在也被转储了。 编译器一个接一个地遍历实现文件,引入包含, 并在一个很长的传球中将它们贯穿。然后它或多或少地忘记了它刚刚做的所有事情并继续下一个实现文件,拉入包含,然后继续前进。

        它首先编译 Terminallog.cc,找到那里所有内容的代码,以及模板的其余部分。但它不会生成 Terminallog::operator

        从第 1 部分和第 2 部分开始,当编译器编译 testMain.cpp 时, 在 terminallog.hh 中转储后,它必须使用以下内容:

        class Terminallog {
        public:
        
            Terminallog();
            Terminallog(int);
            virtual ~Terminallog();
        
            template <class T>
            Terminallog &operator<<(const T &v);
        
        private:
        
        };
        
        int main() {
            Terminallog clog(3);
            clog << "bla";
            clog << "bla";
            return 0;
        }
        

        从编译器的角度来看,一切似乎都很酷。 operatoroperator<<(T = const string)写了一个调用标记, constructor(int) 完全一样。它看不到实现,也不在乎。瞧,您的全能运营商。 它不知道此时 T 必须有一个带有 ostream 的 operator

        然后链接器运行,替换 call to constructor(int) 标记,搜索是否找到它的实现,在 terminallog.cc.o 中找到它。 然后它试图找到从未生成过的Terminallog::operator&lt;&lt;(string)。不在这里不在terminallog.cc.o,无处可去,惨遭失败。

        编译器只有在看到函数体时才会为模板函数生成代码。您可以通过简单地尝试 终端日志堵塞(3); 堵塞

        这应该告诉您,您必须在标头中为函数提供主体,以便可以将其转储到使用它的每个 .cc 文件中,并且编译器可以即时生成所需的内容。 否则你必须猜测可能会为 T 传入什么,这比略微臃肿的头文件更令人讨厌。

        (我在这里写的内容有一些简化和不准确之处,但它的主旨应该是正确的。)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-04-07
          • 1970-01-01
          • 1970-01-01
          • 2014-11-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多