Оператор перегрузки << для шаблонного класса - PullRequest
5 голосов
/ 28 ноября 2009

Я пытаюсь реализовать метод для двоичного дерева, которое возвращает поток. Я хочу использовать поток, возвращенный в методе, чтобы показать дерево на экране или сохранить дерево в файле:

Эти два метода находятся в классе двоичного дерева:

Объявления:

void streamIND(ostream&,const BinaryTree<T>*);
friend ostream& operator<<(ostream&,const BinaryTree<T>&);

template <class T>
ostream& operator<<(ostream& os,const BinaryTree<T>& tree) {
    streamIND(os,tree.root);
    return os;
}

template <class T>
void streamIND(ostream& os,Node<T> *nb) {
    if (!nb) return;
    if (nb->getLeft()) streamIND(nb->getLeft());
    os << nb->getValue() << " ";
    if (nb->getRight()) streamIND(nb->getRight());
}

Этот метод находится в классе UsingTree:

void UsingTree::saveToFile(char* file = "table") {
    ofstream f;
    f.open(file,ios::out);
    f << tree;
    f.close();
}

Поэтому я перегрузил оператор «<<» класса BinaryTree для использования: cout << tree и ofstream f << tree, но я получаю следующее сообщение об ошибке: неопределенная ссылка на `operator << (std :: basic_ostream > &, BinaryTree &) '

P.S. В дереве хранятся объекты Word (строка с целым числом).

Надеюсь, вы понимаете мой плохой английский. Спасибо! И я хотел бы знать хороший текст для начинающих о STL, который объясняет все необходимое, потому что я трачу все свое время на ошибки, подобные этой.

РЕДАКТИРОВАТЬ: объявляется дерево в saveToFile (): BinaryTree tree.

Ответы [ 4 ]

8 голосов
/ 28 ноября 2009

Проблема в том, что компилятор не пытается использовать предоставленную вами шаблонную operator<<, а скорее не шаблонную версию.

Когда вы объявляете друга внутри класса, вы внедряете объявление этой функции во вложенную область видимости. Следующий код имеет эффект объявления (и не определения) свободной функции, которая принимает аргумент non_template_test с помощью константной ссылки:

class non_template_test
{
   friend void f( non_template_test const & );
};
// declares here:
// void f( non_template_test const & ); 

То же самое происходит с шаблонными классами, даже если в этом случае это немного менее интуитивно понятно. Когда вы объявляете (а не определяете) функцию друга в теле класса шаблона, вы объявляете свободную функцию с этими точными аргументами. Обратите внимание, что вы объявляете функцию, а не функцию шаблона:

template<typename T>
class template_test
{
    friend void f( template_test<T> const & t );
};
// for each instantiating type T (int, double...) declares:
// void f( template_test<int> const & );
// void f( template_test<double> const & );

int main() {
    template_test<int> t1;
    template_test<double> t2;
}

Эти свободные функции объявлены, но не определены. Сложность в том, что эти свободные функции не являются шаблоном, а объявляются обычными свободными функциями. Когда вы добавляете функцию шаблона в микс, вы получаете:

template<typename T> class template_test {
   friend void f( template_test<T> const & );
};
// when instantiated with int, implicitly declares:
// void f( template_test<int> const & );

template <typename T>
void f( template_test<T> const & x ) {} // 1

int main() {
   template_test<int> t1;
   f( t1 );
}

Когда компилятор запускает основную функцию, он создает шаблон template_test с типом int, который объявляет свободную функцию void f( template_test<int> const & ), которая не является шаблонной. Когда он находит вызов f( t1 ), есть два f символа, которые соответствуют: не шаблон f( template_test<int> const & ), объявленный (и не определенный), когда был создан экземпляр template_test, и шаблонная версия, которая объявлена ​​и определена в 1. Версия без шаблонов имеет приоритет, и компилятор соответствует ей.

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

Что мы можем сделать? Есть два разных решения. В первом случае мы заставляем компилятор предоставлять функции без шаблонов для каждого экземпляра типа. Во втором случае мы объявляем шаблонную версию как друга. Они немного отличаются, но в большинстве случаев эквивалентны.

Если компилятор сгенерирует функции без шаблонов для нас:

template <typename T>
class test 
{
   friend void f( test<T> const & ) {}
};
// implicitly

В результате создается столько бесплатных шаблонных функций, сколько необходимо. Когда компилятор находит объявление друга в шаблоне test, он не только находит объявление, но также и реализацию и добавляет оба в охватывающую область.

Создание шаблонной версии для друга

Чтобы сделать шаблон другом, мы должны уже объявить его и сообщить компилятору, что наш друг на самом деле является шаблоном, а не свободной от шаблонов свободной функцией:

template <typename T> class test; // forward declare the template class
template <typename T> void f( test<T> const& ); // forward declare the template
template <typename T>
class test {
   friend void f<>( test<T> const& ); // declare f<T>( test<T> const &) a friend
};
template <typename T> 
void f( test<T> const & ) {}

В этом случае перед объявлением f в качестве шаблона мы должны предварительно объявить шаблон. Чтобы объявить шаблон f, мы должны сначала объявить шаблон test. Объявление друга изменено и теперь включает в себя угловые скобки, которые указывают, что элемент, который мы делаем другом, на самом деле является шаблоном, а не свободной функцией.

Вернуться к проблеме

Возвращаясь к вашему конкретному примеру, самое простое решение состоит в том, чтобы компилятор генерировал функции для вас, вставляя объявление функции friend:

template <typename T>
class BinaryTree {
   friend std::ostream& operator<<( std::ostream& o, BinaryTree const & t ) {
      t.dump(o);
      return o;
   }
   void dump( std::ostream& o ) const;
};

С помощью этого кода вы заставляете компилятор генерировать не шаблонизированный operator<< для каждого экземпляра типа, и сгенерированные делегаты функции в методе dump шаблона.

3 голосов
/ 28 ноября 2009

вам не нужно объявление оператора шаблона, и вы должны объявить оператор "друг", чтобы ваш класс предоставил доступ к другим классам, в данном случае std :: cout

friend std::ostream& operator << ( std::ostream& os, BinaryTree & tree )
{
    doStuff( os, tree );
    return os;
}

рекомендуемое чтение: http://www.parashift.com/c++-faq-lite/friends.html

3 голосов
/ 28 ноября 2009

При перегрузке оператора << вы хотите использовать постоянную ссылку:

template <class T>
std::ostream& operator << (std::ostream& os, const BinaryTree<T>& tree) 
{
    // output member variables here... (you may need to make
    // this a friend function if you want to access private
    // member variables...

    return os;
}
2 голосов
/ 28 ноября 2009

Убедитесь, что полные определения шаблонов (а не только прототипы) находятся в файле include (т.е. в .h, .hpp), поскольку шаблоны и отдельная компиляция не работают вместе.

Я не знаю, какой компоновщик использует @Dribeas, но это определенно может привести к тому, что компоновщик GNU выдаст неопределенную ошибку ссылки.

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