运算符重载 c++ / 在哪里放置我的代码?

发布于 2024-10-27 04:55:32 字数 1461 浏览 1 评论 0原文

今天我超载了<<我的一个类中的运算符:

#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

Today I overloaded the << operator in one of my classes:

#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

As you can see I defined the overloaded operator in my header file and I went on implementing it in my .cc file:

//stripped code

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

//stripped code

Afterwards I created a main.cpp file using my new class:

#include "inc/terminallog.hh"

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

and i went on compilying:

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

BAM! a stupid linker error and I still have no idea where it comes from. I played around a bit and noticed that putting the implementation of my overloaded operator in my header file solves all problems. Now I am even more confused than before.

Why can't I put the implementation of the overloaded operator in my .cc file? Why is it running smoothly when I put it in my header file?

Confused thanks in advance

ftiaronsem

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

零崎曲识 2024-11-03 04:55:32

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

The compiler must see the implementation to be able to use the template. Usually that means you put it in the header.

柏林苍穹下 2024-11-03 04:55:32

可以在 cpp 文件中保留实现,但您需要为您使用的每种类型声明模板的用法。请参阅 Parashift C++ 常见问题解答了解更详细的说明。

在您的情况下,您必须在 cpp 文件中的某个位置写入该行:

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

It is possible to keep implementation in cpp file, but you need to declare usage of your template for every type you are using it with. Please see Parashift C++ Faq for more detailed explanation.

In your case, you have to write that line somewhere in your cpp file:

template Terminallog &Terminallog::operator<<(const char* &v);
绻影浮沉 2024-11-03 04:55:32

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

In addition to @Bo's answer: you should read the article in C++ FAQ Lite: http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12 and further on.

时间你老了 2024-11-03 04:55:32

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

如果编译器在您的示例中仅看到

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

您可以使用运算符<<的行绝对有任何东西!我的字面意思是苹果和李子。
那个操作员一定是万能的!

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

第二部分:
了解#include 的作用。
C/C++(遗憾的是)不知道接口和实现之间有什么真正的区别,它没有模块系统。 headers、cpps都只是一个约定。就可以愉快的写了
#include。没有什么能阻止你。
#include"inc/terminallog.hh" 语句只是将标头的所有内容转储到当前文件中(以及其中 #included 的所有内容)。这是我们使用 include 保护的一个稍微令人沮丧的原因 #ifndef TERMINALLOG_HH 因为像 string 或类似的标头可能会被转储到我们的文件中一百次或更多次,并且编译器会不断地抛出重新定义错误。
实际上,如果您添加了`#include,它很可能会消除您的错误,因为 Terminallog 的实现现在也被转储了。
编译器逐一检查实现文件,引入包含文件,
并通过一次很长的传球将它们贯穿。然后它或多或少地忘记了刚刚所做的一切,并继续执行下一个实现文件,引入包含内容,然后继续前进。

它首先编译 Terminallog.cc,查找其中所有内容的代码以及模板的其余部分。但它不会生成 Terminallog::operator<<(string) ,因为它从未在那里使用过。

从第 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;
}

从编译器的角度来看,一切看起来都很酷。运算符<<有一个模板参数,
因此编译器为operator<<(T = const string)编写一个调用标记,
constructor(int)完全相同。它看不到实现并且不关心。瞧,你的全能接线员。
它不知道 T 必须有一个运算符 <<此时与 ostream 一起!尽管如此,这是合法的,它只是希望您能够向它提供一些关于实际应该生成哪些代码的提示,如果不在这个文件中,也许在下一个文件中,也许在上一个文件中。链接器会知道。编译器只记住“如果我找到一个函数体模板,我将使用 T=string 为其生成代码”。但不幸的是它永远没有机会。

然后链接器运行,替换调用构造函数(int)标记,搜索是否找到它的实现,并在terminallog.cc.o中找到它。
然后它尝试查找从未生成的 Terminallog::operator<<(string) 。不在这里,不在terminallog.cc.o,无处可去,而且惨败。

仅当编译器也看到函数体时,它才会为模板函数生成代码。你可以尝试简单地做
终端日志堵塞(3);
木屐<< “布拉”;
在terminallog.cc中Terminallog的构造函数中。
当编译器看到它时,它将生成 Terminallog::operator<<(string),链接器将在编译后的terminallog.cc.o 目标文件中找到它。

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

(我在这里写的内容有一些简化和不准确的地方,但其要点应该是合理的。)

Templates have a special property: Their "implementation" can be thought of as a part of their signature.

If the compiler would, in your example, see only the line

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

you could use operator<< with absolutely anything!! And I literally mean Apples and Prunes.
That operator would be almighty!

Your operator IMPLEMENTATION however, requires a type that can be piped into cout!!
Which is not true for many types! Try to pipe your Terminallog class into cout.
That cannot work. And the compiler can only check for such compliance,
if you tell it what you want to do with T.

The second part:
Understand what #include does.
C/C++ (sadly)knows no real difference between interface and implementation, it has no module system. Headers, cpps are all just a convention. You can happily write
#include <something.cpp>. Nothing will stop you.
The #include"inc/terminallog.hh" statement just dumps everything the header into the current file (and all the stuff #included there as well). This is the slightly depressing reason why we use include guards a'la #ifndef TERMINALLOG_HH since headers like string or similar may get dumped into our file a hundred times or more, and the compiler would constantly throw redefinition errors.
Actually, if you added `#include it would most likely remove your error, since the implementation of Terminallog is now dumped in as well.
The compiler goes through the implementation files one after the other, pulls in includes,
and runs them through in one very long pass. Then it forgets more or less everything it just did and goes on to the next implementation file, pulls in the includes, and plods on.

It first compiles Terminallog.cc, finds code for everything there, and also the rest of the template. It generates no Terminallog::operator<<(string) though, because it is never used there.

From part 1 and 2 follows, that when the compiler compiles testMain.cpp,
after dumping in terminallog.hh, this is what it has to work with:

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;
}

From the compilers point of view all seems cool. operator<< has a template parameter,
so the compiler writes a call marker for the operator<<(T = const string),
completely the same for the constructor(int). It doesn't see the implementation and doesn't care. Voila, your almighty operator.
It does not know that T must have an operator<< with ostream at this point! Still, this is legal and it just hopes you will be providing it some hints as to what code should actually be generated, if not in this file, maybe in the next one, maybe in a previous one. The linker will know. The compiler just remembers "if I find a function body template, I will generate code for it with T=string". But unfortunately it never gets the chance.

The linker then runs through, replaces the call to constructor(int) marker, searches if it finds an implementation for it, finds it in terminallog.cc.o.
Then it tries to find Terminallog::operator<<(string) which has never been generated. Not here not in terminallog.cc.o, nowhere, and fails miserably.

The compiler generates code for a template function only when it sees the function body as well. You can try by simply doing
Terminallog clog(3);
clog << "bla";
in the constructor of Terminallog in terminallog.cc.
When the compiler sees that, it will generate Terminallog::operator<<(string), and the linker will find it in the compiled terminallog.cc.o object file.

This should tell you, that you have to provide the body to a function in the header, so that it can be dumped into every .cc file that uses it and the compiler can generate what it needs on the fly.
Otherwise you have to guess what may be passed in for T, which is even more of a nuisance than slightly bloated header files.

(There are some simplifications and inaccuracies in what I wrote here, but the gist of it should be sound.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文