views:

47

answers:

3

Hi,

So I'm trying to pick C++, and to do so I decided to write a generic Group class using templates, that takes a Type and size as template parameters:

in group.h:

#ifndef __GROUP_H
#define __GROUP_H
#define MAX_SIZE 10

/**********************************************************
 * Define a Group class that handles a collection of members
 * of some Type
 **********************************************************/
template <class Type, int max>
class Group {
  private:
      std::string name;
      int count, size;
      Type * members[max];

  public:
      Group();
      Group(const std::string & _name);
      ~Group();

      // display instance info
      virtual void show();

      // add member
      void add_member(Type &);

      // list memebers
      void list();

      // name setter/getter
      void set_name(const std::string &);
      const std::string & get_name();

};

#endif

group.cc:

/**********************************************************
 * class methods for Group
 **********************************************************/
template <class Type, int max>
Group<Type, max>::Group() : count(0), size(max), name("New Group") {};

template <class Type, int max>
Group<Type, max>::Group(const std::string & _name) : name(_name), count(0), size(max) {};

template <class Type, int max>
Group<Type, max>::~Group() {
  int i = 0; 
  while(i < this->count) {
    delete this->members[i];
    ++i;
  }
}

template <class Type, int max>
void Group<Type, max>::show() {
    std::cout << "<#Group - Name: " << this->name << ", Members: " << this->count << "/" << this->size << " >\n";
}

template <class Type, int max>
void Group<Type, max>::add_member(Type & member) {
    if (this->count < this->size) {
        this->members[this->count] = &member;
        this->count++;
    } else {
        std::cout << "Error - this Group is full!\n";
    }
}

template <class Type, int max>
void Group<Type, max>::list() {
    int i = 0;
    std::cout << "The following are members of the Group " << this->name <<":\n";
    // assumes the member has a show() method implemented
    while (i < this->count) {
        std::cout << i << ". ";
        (this->members[i])->show();
        ++i;
    }
}

template <class Type, int max>
void Group<Type, max>::set_name(const std::string & _name) {
    this->name = _name;
}

template <class Type, int max>
const std::string & Group<Type, max>::get_name() {
  return this->name;
}

I also implemented a Person class and an Employee class (which inherits from Person) and both work and have the show() method.

My main looks like this:

test.cc

#include <iostream>
#include "group.h" // this also has the declarations and implementation for Person/Employee

int main (int argc, char const *argv[])
{
    // Person ctor takes name and age
    Person p1("John", 25); 
    Person p2("Jim", 29);

    // Group takes name to init
    Group <Person, 5> g("Ozcorp");
    g.add_member(p1);
    g.add_member(p2);
    g.list();    
}

I compiled it with a simple Makefile:

test: test.cc group.o
    g++ -o test test.cc group.o

group.o: group.h group.cc
    g++ -c group.cc

And finally (whew), when I ran it with ./test got the following errors:

Undefined symbols:
  "Group<Person, 5>::list()", referenced from:
      _main in ccaLjrRC.o
  "Group<Person, 5>::Group(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)", referenced from:
      groups()    in ccaLjrRC.o
      _main in ccaLjrRC.o
  "Group<Person, 5>::~Group()", referenced from:
      groups()    in ccaLjrRC.o
      _main in ccaLjrRC.o
      _main in ccaLjrRC.o
  "Group<Person, 5>::add_member(Person&)", referenced from:
      _main in ccaLjrRC.o
      _main in ccaLjrRC.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
make: *** [test] Error 1

If you made it this far - thanks - and I'd appreciate some insight on why this happens. I tried to share as much as possible from the code (obviously), so forgive me if it's way to much. Source was compiled with g++ 4.2.1 on mac osx 10.6.4. Also, any style / good coding-habits tips will be appreciated. Thanks!

+3  A: 

The compiler must see the template definitions if it wants to instantiate them. The easiest way to achieve this is to put every piece of templated code (including all definitions) into the header file.

Philipp
+1, and remember that if the template definitions are in the header they must be inlined (either `inline` keyword or define them inside the class definition) if you do not want to go from 'symbol not found' to 'duplicate symbol' type of errors.
David Rodríguez - dribeas
This works, though I thought the idea of separating the header from implementation file (creating group.h and group.cc) was to show the API to the user (ie - the function declarations) while being able to hide their implementation (by providing the *.o/*.obj file). So how is that accomplished if I include everything (declaration + implementation) in the .h file?
sa125
@sa125: See my answer about .hxx : definitions can be exported in an additionnal header file, included by the main .h, hiding the implementation.
Scharron
+2  A: 

Templated class member definition does not go to .cc / .cpp files. They go in the .h, or in a .hpp / .hxx file included by the .h The rational is that .cc / .cpp files are used to build objects files. However, with templated code, objects cannot be created before templated arguments are substituted. Therefore, their implementation must be available to all pieces of code instantiating them: in a .h like file.

Scharron
+1  A: 

Template instantiation can be either implicit (performed by the compiler) or explicit (user requested). For implicit instantiations, the compiler must see the template function definition at the place of call --which as Philipp has already said--, is easiest done by adding the template function definitions into the header file.

The alternative is somehow limited: if you know all instantiations of your template upfront, you can leave the template function definitions elsewhere and explicitly instantiate them. In your case, at the end of group.cpp you could add:

template class Group<Person, 5>;

The advantage (if many parts of the code use this template and it is expensive to instantiate) is that the template code will be compiled only once in one translation unit and later linked, which can reduce compile time, but the huge disadvantage is that the set of valid instantiations must be known in advance and the template cannot be used with other types --limiting the generality of the solution.

If in doubt, provide the definitions in the header file, and remember that the function definitions must be inlined or else you will hit the opposite linker error: "duplicate symbol" if you include the header from different translation units. A member function is inlined by default if it is defined within the class curly braces or if it has the inline keyword.

David Rodríguez - dribeas