开发者

operator overloading c++ / where to put my code?

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

#ifndef TERMINALLOG_HH
#define TERMINA开发者_如何学GoLLOG_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


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


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


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.


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.)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜