C++ Trennung der Definition von Deklaration bei Template Klassen

Rossibaer

Lieutenant
Registriert
Apr. 2007
Beiträge
754
Hallo zusammen,

seit letztem Wochenende hänge ich mal wieder in den Untiefen des C/C++ fest. Also folgendes:

Kurz zusammengefasst bekomme ich es einfach nicht hin die Deklaration der Klasse und deren Member von deren Definition zu trennen.

Ich möchte in die Header Datei Array.h nur die Deklaration der Template Klasse schreiben, wie ich es aus meinen frühen Anfängen gewohnt bin. Die Definition erfolgt dann in der dazu gehörigen CPP Datei. Bei "normalen" Klassen habe ich das Problem nicht. Das Kompilieren funktioniert, das Linken geht auch fehlerfrei. Wenn ich jetzt aber die Template Klasse "Array" nach dem selbigen Schema trenne, dann bekomme ich stets für jeden Member den ich im restlichen Programm-Code verwende mindestens einen Fehler beim Linken (das Kompilieren zeigt erstmal keine Fehler, bin jedoch noch nicht soweit gegangen den Compiler auf pedantisch ... einzustellen):

Code:
Fehler	1	error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: virtual __thiscall Array<int>::~Array<int>(void)" (??1?$Array@H@@UAE@XZ)" in Funktion ""public: __thiscall Engine::Engine(void)" (??0Engine@@QAE@XZ)".	Engine.obj	
Fehler	2	error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: void __thiscall Array<int>::Clear(void)" (?Clear@?$Array@H@@QAEXXZ)" in Funktion ""public: __thiscall Engine::Engine(void)" (??0Engine@@QAE@XZ)".	Engine.obj	
Fehler	3	error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: void __thiscall Array<int>::Add(int)" (?Add@?$Array@H@@QAEXH@Z)" in Funktion ""public: __thiscall Engine::Engine(void)" (??0Engine@@QAE@XZ)".	Engine.obj	
Fehler	4	error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""public: __thiscall Array<int>::Array<int>(void)" (??0?$Array@H@@QAE@XZ)" in Funktion ""public: __thiscall Engine::Engine(void)" (??0Engine@@QAE@XZ)".	Engine.obj

Wenn ich exakt den gleichen Code der Array.cpp Datei ans Ende der Datei Array.h verschiebe, dann ist selbst das Linken ohne Fehler und das Programm läuft erstmal wie gewünscht.

Hier beide Dateien

Array.h:

Code:
#ifndef ARRAY_H
#define ARRAY_H

#include "object.h"
#include "Assert.h"

template <class T> class Array :
  public Object
{

public:
  Array();
  ~Array();
  void Add(T item);
  void Clear();
  T GetValue(int index);
  void SetValue(int index, T item);

private:
  T* content;
  int count;
  int maxsize;
  void Resize(int new_size);
};

#endif

Array.cpp:

Code:
#include "Array.h"
//
template <class T> Array<T>::Array()
{
  this->count = 0;
  this->content = NULL;
  this->Resize(5);
}
//
template <class T> Array<T>::~Array()
{
  delete[] this->content;
}
//
template <class T> void Array<T>::Clear()
{
  for(int index = 0; index < this->count; index++)
    this->content[index] = NULL;
  this->count = 0;
}
//
template <class T> void Array<T>::Add(T item)
{
  if(this->count == this->maxsize)
    this->Resize(this->maxsize * 2);
  this->content[this->count++] = item;
}
//
template <class T> T Array<T>::GetValue(int index)
{
  assert(index >= 0 && index < this->count);
  return this->content[index];
}
//
template <class T> void Array<T>::SetValue(int index, T item)
{
  assert(index >= 0 && index < this->count);
  this->content[index] = item;
}
//
template <class T> void Array<T>::Resize(int new_size)
{
  assert(new_size > 0);
  T* temp = new T[new_size];
  int index = 0;
  if(this->content)
  {
    for(; index < this->count; index++)
    {
      temp[index] = this->content[index];
      this->content[index] = NULL;
    }
  }
  for(; index < new_size; index++)
    temp[index] = NULL;
  if(this->content) delete[] this->content;
  this->content = temp;
  this->maxsize = new_size;
}
//

Als Entwicklungsumgebung verwende ich Visual Studio 2005 und dessen Compiler.

Falls euch noch etwas an Input fehlen sollte, dann schreibt welche Infos ihr bräuchtet.

Vorerst helfe ich mir damit, dass ich eben alles in die Header Dateien packe. Jedoch ist das nach meinem Verständniss nicht wirklich der Sinn der Sache.

Ich würde jetzt mal vermuten, dass ich unbewußt die template Definition in der CPP Datei "überschreibe" und deswegen der Linker nicht weiß, welchen Code er nun wie linken soll.

Danke schon mal dafür, dass ihr bis jetzt durchgehalten und meine Beschreibung des Problems gelesen habt.

Viele Grüße
Rossibaer :)

PS: Bitte keine Tipps ala Google ist dein Freund, weil ich genau diesen "Freund" verwendete um überhaupt die Deklaration von der Definition zu trennen. Der "Stroustrup" hat mich ebenfalls erfolgreich abgehängt bzw. mehr Fragen als Antworten geliefert... ;)
 
Zuletzt bearbeitet:
Hi,

kurz: Das wird so nicht gehen. Bei Templates müssen Deklaration und Definition immer "zusammen" erfolgen. Da gibts dann i.A. 3 Möglichkeiten


1. Beides in eine Datei (z.B. MyClass.h):
============================
PHP:
template<class T>
class MyClass
{
  /// Constructor declaration
  MyClass();
  /// Destructor declaration
  virtual ~MyClass();

  /// Declaration of a method
  void Method(int);
};

// Constructor definition
template<class T>
MyClass<T>::
MyClass()
{
}

// Destructor definition
template<class T>
MyClass<T>::
~MyClass()
{
}

//  Definition of the member method
template<class T>
void
MyClass<T>::
Method(int i)
{
}


2. Deklaration (MyClass.h) und Definition (MyClass.txx) "trennen" wobei MyClass.txx am Ende des Headers inkludiert wird:
========================
MyClass.h:
PHP:
template<class T>
class MyClass
{
  /// Constructor declaration
  MyClass();
  /// Destructor declaration
  virtual ~MyClass();

  /// Declaration of a method
  void Method(int);
};

#include MyClass.txx

MyClass.txx
PHP:
// Constructor definition
template<class T>
MyClass<T>::
MyClass()
{
}

// Destructor definition
template<class T>
MyClass<T>::
~MyClass()
{
}

//  Definition of the member method
template<class T>
void
MyClass<T>::
Method(int i)
{
}

3. Explizite Template-Instantiierung:
==========================
MyClass.h:
PHP:
template<class T>
class MyClass
{
  /// Constructor declaration
  MyClass();
  /// Destructor declaration
  virtual ~MyClass();

  /// Declaration of a method
  void Method(int);
};

/// Explicit template instantiation for double
typedef class MyClass<double> MyClassDouble;

MyClass.txx
PHP:
// Constructor definition
template<class T>
MyClass<T>::
MyClass()
{
}

// Destructor definition
template<class T>
MyClass<T>::
~MyClass()
{
}

//  Definition of the member method
template<class T>
void
MyClass<T>::
Method(int i)
{
}

Instantiations.cxx
PHP:
#include "MyClass.txx"

/// Explicit instantiations
template MyClassDouble;


P.S.:
Include-Wächter nicht vergessen, für C++ Dateien verwende ich die Endung .cxx/.txx da .cpp für mich der C-PreProcessor ist.

Noch was zu den Vor- und Nachteilen der verschieden Varianten, bzw. Einsatzszenarien:
Version 1 und 2:
Vom programmiertechnischen Standpunkt gesehen sind die beiden Varianten identisch. Version 1
verwende ich persönlich nie, da ich eine strikte Trennung von Deklaration und Definition als extrem wichtig erachte, und einen "sauberen" Header bevorzuge (v.a. in Kombination mit Tools zur Dokumentation). Wenn du deinen Code weitergeben willst muss du bei Version 2 halt 2 Dateien herausgeben. Vorteil dieser beiden Varianten: Jeder Template Parameter ist zumindest theoretisch möglich. Nachteil: ClosedSource ist unmöglich und bei jeder Änderung der "Template-Definition" muss jede Klasses neu kompiliert werden die den Template-Header inkludiert.
Version 3:
Vorteil: Bei einer Änderung einer Template-Definition wird nur Instantiations.cxx neu kompiliert, ClosedSource ist möglich. Nachteil: Der Benutzer kann nur explizite Templates verwenden (in obigem Beispiel ist also nur der Template-Parameter double) möglich
 
Zuletzt bearbeitet: (Vor- und Nachteile)
Hallo Gluehwurm,

vielen lieben Dank für deine ausführliche Antwort zu dem Thema. Ich war schon gestern hellauf begeistert als ich das las, aber kam erst jetzt dazu was zu schreiben.

Zu den Varianten die du beschrieben hast:

Variante 1: Ist das was ich momentan praktiziere und ich bin da mit dir einer Meinung. Ich bin ebenfalls für eine klare Trennung und möchte den Header möglichst übersichtlich und kompakt halten. Was ja auch manchmal bei mir funktioniert ;-)

Variante 2: Ist das was ich vermutet aber nicht zu machen gewagt hatte. Ich sollte eben doch auch mal was riskieren und den Vermutungen nachgeben. :-D Jedenfalls werde ich zukünftig die Variante 2 anwenden.

Variante 3: Ist für mich interessant zu wissen und auch so einigermaßen klar wie es geht, danke nochmal für das Beispiel, damit konnte ich deinen Ausführungen sehr gut folgen. Ich werds mal als "Good to know" einstufen und bei Gelegenheit anwenden.

Allgemein habe ich mir in meinem konkreten Programm/Projekt noch nicht die Gedanken gemacht, ob ich es überhaupt verteilen möchte. D.h. OpenSource oder ClosedSource sind 2 Dinge, die ich mal ganz weit nach hinten verschiebe. Momentan bin ich der "Herr" über den Code und die Executables und werde vorerst auch der einzige "Anwender" sein. Es ist mehr so eine kleine Freizeitbeschäftigung um mich selber zu fordern und die grauen Zellen auf Trab zu halten, statt immer nur diese .Net Klick-Klack-Einheitssosse in der Arbeit zu verrühren.

Die Dateiendung cpp ist bei VS eine default Vorgabe, bei der ich es bisher auch gelassen habe. Und ich kann mich noch an meinen sogenannten "Berufsschullehrer" erinnern, der cpp als eine Abkürzung von "C Plus Plus" bezeichnete. Danke das du mir auch hier eine andere Sichtweise gezeigt hast. Da aber die Ausbildung lange zurück liegt und ich mich quasi nur noch an so einige wenige Syntaxelemente in C++ erinnern kann, ist das Zeugs von damals eh unwichtig, da ich das Ganze nun sowieso wieder neu erlerne ...

Also um das nun zu einem Ende zu bringen, vielen Dank, dein Nickname hat sich jetzt bei mir eingeprägt und mal sehen, vielleicht kommen wir mal wieder zu einem Gedankenaustausch ...

Bis dahin alles Gute
Rossibaer

EDIT 17 JUL 2010:
Kleine Anmerkung für diejenigen die die Trennung wie in Variante 2 und 3 machen wollen und Visual Studio einsetzen.
Die Defintion der Member sollte in einer TXX Datei erfolgen, sonst meldet der Compiler bereits diverse Fehler obwohl alles syntaktisch richtig ist. Ich hatte zuerst dummerweise "cxx" verwendet, jedoch ging dies nicht wie schon gesagt. Ebenso lies sich die Formatierung (Schlüsselwörter blau etc.) und Intellisense in der TXX Datei nur durch eine zusätzliche Option des Texteditors erreichen:
Menü: Extras / Optionen auswählen
im Baum Texteditor / Dateierweiterungen auswählen
Erweiterung "TXX" mit Zuordnung "Microsoft Visual C++" hinzufügen
Danach sollten die Dateien wie gewohnt mit Formatierung und Intellisense bearbeitbar sein.
 
Zuletzt bearbeitet: (txx statt cxx hinzugefügt)

Ähnliche Themen

Zurück
Oben