перегрузка оператора c ++ / куда поместить мой код? - PullRequest
5 голосов
/ 27 марта 2011

Сегодня я перегрузил оператор << в одном из моих классов: </p>

#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

BAM!глупая ошибка компоновщика, и я до сих пор не знаю, откуда она взялась.Я немного поиграл и заметил, что размещение реализации моего перегруженного оператора в заголовочном файле решает все проблемы.Теперь я еще больше запутался, чем раньше.

Почему я не могу поместить реализацию перегруженного оператора в мой файл .cc?Почему он работает гладко, когда я помещаю его в мой заголовочный файл?

Смущен, спасибо заранее

ftiaronsem

Ответы [ 4 ]

4 голосов
/ 27 марта 2011

Компилятор должен видеть реализацию, чтобы иметь возможность использовать шаблон. Обычно это означает, что вы помещаете это в заголовок.

1 голос
/ 27 марта 2011

Можно сохранить реализацию в файле cpp, но вам нужно объявить использование вашего шаблона для каждого типа, с которым вы его используете. Пожалуйста, смотрите Parashift C ++ Faq для более подробного объяснения.

В вашем случае вы должны написать эту строку где-нибудь в вашем файле cpp:

template Terminallog &Terminallog::operator<<(const char* &v);
1 голос
/ 27 марта 2011

В дополнение к ответу @ Бо: вы должны прочитать статью в C ++ FAQ Lite: http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12 и далее.

0 голосов
/ 27 марта 2011

Шаблоны имеют специальное свойство: их «реализация» может рассматриваться как часть их подписи.

Если компилятор в вашем примере увидит только строку

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

Вы можете использовать оператор << абсолютно с любым !!И я буквально имею в виду яблоки и чернослив.Этот оператор был бы всемогущим! </p>

Однако вашему оператору РЕАЛИЗАЦИЯ требуется тип, который можно отправить в cout !!Что не верно для многих типов!Попробуйте передать свой класс Terminallog в cout.Это не может работать.И компилятор может только проверить на такое соответствие, если вы скажете ему, что вы хотите сделать с T.

Вторая часть: понять, что делает #include.C / C ++ (к сожалению) не знает реальной разницы между интерфейсом и реализацией, у него нет модульной системы.Заголовки, cpps - это всего лишь соглашение.Вы можете счастливо написать #include <something.cpp>.Ничто не остановит вас.Оператор #include"inc/terminallog.hh" просто сбрасывает все заголовки в текущий файл (и все вещи, которые там тоже включены #).Это немного удручающая причина, по которой мы используем include guard'a #ifndef TERMINALLOG_HH, поскольку заголовки, такие как string или аналогичные, могут быть выгружены в наш файл сотни и более раз, и компилятор будет постоянно выдавать ошибки переопределения.На самом деле, если вы добавите `#include, это, скорее всего, удалит вашу ошибку, так как реализация Terminallog теперь также сбрасывается.Компилятор просматривает файлы реализации один за другим, извлекает включения и запускает их за один очень длинный проход.Затем он забывает более или менее все, что он только что сделал, и переходит к следующему файлу реализации, извлекает include и запускает.

Сначала компилирует Terminallog.cc, находит код для всего там, а такжеОстальная часть шаблона.Он не генерирует Terminallog :: operator << (string), хотя, потому что он там никогда не используется. </p>

Из частей 1 и 2 следует, что когда компилятор компилирует testMain.cpp, после выгрузки в терминалlog.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;
}

С точки зрения компиляторов все выглядит круто.Оператор << имеет параметр шаблона, поэтому компилятор записывает маркер вызова для <code>operator<<(T = const string), то же самое для constructor(int).Он не видит реализацию и не заботится.Вуаля, ваш всемогущий оператор.Он не знает, что у T должен быть оператор << с ostream в этой точке!Тем не менее, это законно, и он просто надеется, что вы дадите ему несколько подсказок о том, какой код должен быть сгенерирован, если не в этом файле, возможно, в следующем, может быть, в предыдущем.Компоновщик будет знать.Компилятор просто запоминает «если я найду шаблон тела функции, я сгенерирую для него код с T = string».Но, к сожалению, он никогда не получает такой возможности. </p>

Затем работает компоновщик, заменяет маркер call to constructor(int), ищет, найдет ли для него реализацию, находит его в Terminallog.cc.o.Затем он пытается найти Terminallog::operator<<(string), который никогда не был сгенерирован.Не здесь, не в файле Terminallog.cc.o, нигде и безуспешно.

Компилятор генерирует код для функции шаблона только тогда, когда он также видит тело функции.Вы можете попробовать просто выполнить Terminallog clog (3);сабо << "бла";в конструкторе Terminallog в Terminallog.cc.Когда компилятор увидит это, он сгенерирует Terminallog :: operator << (string), и компоновщик найдет его в скомпилированном объектном файле Terminallog.cc.o. </p>

Это должно сказать вам, что у вас естьпредоставить тело функции в заголовке, чтобы его можно было выгружать в каждый файл .cc, который его использует, и компилятор может генерировать то, что ему нужно, на лету.В противном случае вы должны угадать, что может быть передано для T, что даже более неприятно, чем слегка раздутые заголовочные файлы.

(В том, что я здесь написал, есть некоторые упрощения и неточности, но суть этогодолжен быть здоровым.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...