Deutsch English
Dein Feedback:
Hat die Seite Deine Erwartung erfüllt? vote3 Ja
vote2 Teilweise
vote1 Nein
Noch ein Kommentar?

Nur falls, Du eine Antwort erwartest, Deine E-Mailadresse

Gegebenenfalls noch Dein Name

Do not change this:
Feedback
Suchen

C / C++

Notizen zu C und C++
22-09-2012 11.37

Links


Literaturliste

Autor Titel
Bjarne Stroustrup The C++ Programming Language 3. Edition
Ulrich Breymann C++ Eine Einführung
Namir Clement Shammas C++ für Dummies

Wie Programme gestartet werden


Ein kurzer Überblick über ELF und wie der Linux Kernel Programme startet.
22-09-2012 11.37

Compiler Variablen

C und C++ Compiler umstellen:

export CC=gcc-3.2 CXX=g++-3.2
06-08-2005 13.09

C / C++ Abstürze debuggen:

$ ulimit -c unlimited
$ gcc -g PRG
$ g++ -g PRG
$ ./PRG
- CRASH -
$ gdb PRG core
run
bt
up
down
print $VAR
break foo.cpp:301
delete 1


DDD - Data Display Debugger

C / C++ Speicherlecks finden

valgrind
valgrind --leak-check=yes --show-reachable=yes ./MYPROGRAM

memprof
alleyoop

Laufzeituntersuchung

GNU gprof
g++ -g -pg foo.cpp -o foo.run
./run
gprof foo.run gmon.out | less
gprof foo.run gmon.out -f Foo::myfunction | less

doxygen

doxygen doxygen (x)emacs
C-c d f
C-c d ;
23-12-2006 12.47

Datei-Gerüst

.h

#ifndef _myh1_h_
#define _myh1_h_

#include <iostream>

namespace my_program {
extern int global1;
}

#endif /* _myh1_h_ */

.cpp

#include <GL/glut.h>


#include "cg3_ex1.h"


using namespace std;


namespace my_program
{
int global1 = 5;
float global2 = 6.0;


/**
* comment here
*/
void foo()
{
}


}


int main(int argc, char **argv)
{
using namespace my_program;
return 0;
}

Makros

In C++ meist nicht nötig, vermeiden.
#define FOO blabla
#define BAR(a,b) 1: a 2: b
(kein ; am Ende der Zeile nötig, FOO und BAR werden einfach durch Text rechts ersetzt).

Bestimmte Bereiche nicht kompilieren:
#define VERSION1
...
#ifdef VERSION1
...
#elseif
...
#endif

Klammern setzte nicht vergessen:
#define SQUARE1(a) a*a
#define SQUARE2(a) (a)*(a)
SQUARE1(1+2) // 1+2*1+2
SQUARE2(1+2) // (1+2)*(1+2)
25-10-2006 23.46

Text ausgeben

Mit printf Text ausgeben
#include <stdio.h>
...
printf ("%s", "Hello World");
printf ("Number a: %d Number b: %ld", 42, 650000L);
Alternativ mit cout Text ausgeben
#include <iostream>
...
using namespace std;
cout << "Number" << 42 << endl;

Header

cout#include <iostream>
std::cout << std::setw(8) << foo << std::endl;#include <iomanip>
fabs(int)#include <cstdlib>
fabs(float)#include <cmath>

Variablen

Normal

int  x;  x=5;  cout << x; // 5

Referenz

int& r=x;      cout << r; // 5

Zeiger

int* y;  y=&x; cout << y; // 5 if x is still valid, trouble otherwise
int* y; y=new int; delete y;
int* z; z=y; cout << z // 5
x=*z; cout << x // 5

Größe der Datentypen

#include <limits>
#include <iostream>

int main()
{
using namespace std;
numeric_limits<int> INT;
...
cout << "INT Min: " << INT.min() << " Max: " << INT.max() << " EP: " << INT.epsilon() << endl;
...
}

int uint short ushort long ulong float double ldouble
min i486 -2147483648 +0 -32768 +0 -2147483648 +0 1.17549e-38 2.22507e-308 3.3621e-4932
min amd64 -9223372036854775808
max i486 +2147483647 +4294967295 +32767 +65535 +2147483647 +4294967295 3.40282e+38 1.79769e+308 1.18973e+4932
max amd64 +9223372036854775807 +18446744073709551615
epsilon i486 0 1.19209e-07 2.22045e-16 1.0842e-19
epsilon amd64
Catching Integer Overflows in C

const

Der Wert von x kann nicht geändert werden
const int               x=4;
Zeiger zeigt auf einen Wert der nicht geändert werden darf
      int const *       z;
const int * z;
Zeiger darf nicht geändert werden, der Wert auf den gezeigt wird schon
      int       * const z;
Zeiger und Wert dürfen nicht geändert werden
      int const * const z;
const int * const z;
Methoden die keine Klassenvariablen ändern immer als const kennzeichnen
class Foo {
public:
int getX() const {
return this->x;
}
}

mutable / const_cast

Manchmal möchte man vielleicht doch eine Klassenvariable in einer Methode ändern die eigentlich nichts am Objekt verändert. Zum Beispiel könnte man zum Debuggen zählen wollen wie oft eine get Methode aufgerufen wurde.
class Foo {
unsigned int statistic_access_counter;
public:
int getX() const {
this->statistic_access_counter++; // Error!
return this->x;
}
}
Das wird der Compiler aber nicht zulassen. Um das zu umgehen gibt es zwei Möglichkeiten.
Entweder man benutzt const_cast um vollen Schreibzugriff zu erlangen:
class Foo {
unsigned int statistic_access_counter;
public:
int getX() const {
Foo * tmp=const_cast<Foo *>(this);
tmp->statistic_access_counter++;
return this->x;
}
}
Besser ist allerdings die Klassenvariable zu kennzeichnen die, weil sie keine wichtigen Auswirkung auf das Objekt hat, trotz const geschrieben werden darf:
class Foo {
mutable unsigned int statistic_access_counter;
public:
int getX() const {
this->statistic_access_counter++;
return this->x;
}
}

Felder / Arrays

int * ptr2=(int*)malloc(155*sizeof(int));
for (int i=0; i<155; i++) {
std::cout << ptr2[i];
}
int  a[3]; // a[0], a[1], a[2]
int b[]= {10,11,42};
char c[]= {'a','z','m'};
int d[2]={10,11,42}; // Error!
int d[7]={10,11}; // a[2]==a[3]==...==a[6]==0
b={10,11,42}; // Error

c string kopieren

Als Beispiel, wie man einen (mit 0 abgeschlossenen) c String kopiert.

mystringcopy(char * a, const char * b) {

// A
while(*b!=0) {
*a=*b;
a++;
b++;
}
*a=0;

// B
while( (*a++=*b++) != 0 );

// C
while( *a++=*b++ );

}
Variante B und C nutzen aus dass die Zuweisung a=b als Rückgabewert b hat.

Strings

char c[3];
c[0]='a';
c[1]='b';
c[2]=0;
char * c1=new char[3];
char * c2="ab";

Int nach String

int i=1242;
int len=(int)floor(log(i)/log(10));
char c[16+l+1];
snprintf(c,sizeof(c),"This is my int: %d",i);
c[sizeof(c)-1]=0;

String nach int

char c[5]="1242";
int i=atoi(c);
char *p, *s;
s = "20y";
int i2 = strtol(s,&p,0); // i2:20 p:y

string

string str2("abc");
C++ strings

Enumeration

enum           { FOO,   BAR   };
enum myenumtyp { FOO, BAR };
typedef enum { FOO, BAR } myenumtyp;
enum myenumtyp { FOO=0, BAR=1 };
Enumerations sind oft sinnvoller als normale Konstanten weil der Compiler z.B. warnen kann wenn man in einer switch Anweisung nicht alle Möglichkeiten überprüft.
21-09-2014 15.22

Funktionen

Normale Funktion mit Wert Rückgabe und Wertkopie im Parameter:
int foo(int x) {
return x++;
}
Wie oben nur mit Konstanter Referenz als Paramater (schneller)
int foo(const int& x) {
return x++;
}
Übergabe per Referenz, die Variable im Paramater kann in der Funktion geändert werden
void foo(int& x) {
x++;
}
Rückgabe per const Referenz. Wichtig: Das Ergebnis muss auch in nen Referenzvariable, sonst bekommt man eine Kopie.
int & foo() {
return x;
}

int a=foo(); // :-(
int & b=foo(); // :-)

Funktionszeiger

Zwei normale Funktionen:
int DoIt1  (float a, int b, int c)
{
return a+b+c;
}

int DoIt2 (float a, int b, int c)
{
return a*b*c;
}

class MyClass {
public:
int DoIt3(float a, int b, int c)
{
return a+b*c;
};
};
Zeiger für die Funktionen definieren:
int (         *foo)(float, int, int);
int (MyClass::*bar)(float, int, int)=&MyClass::DoIt3;
Zeiger mit Werten belegen und aufrufen:
foo = DoIt1;
int result1 = foo(1.0, 2, 3);
int result2 = (*foo)(1.0, 2, 3);

foo = DoIt2;
int result3 = foo(1.0, 2, 3);

MyClass * f;
int result4 = f->DoIt3(1.0, 2, 3);
int result5 = (f->*bar)(1.0, 2, 3);
Zeiger als Parameter übergeben:
void myfunc(int (*foo)(float, int, int))
{
int result = foo(1.0, 2, 3);
}

myfunc(&DoIt1);
myfunc(&DoIt2);

Funktionsargumente

void foo(bool a, bool b=false);
Zweiter Parameter optional, default ist false
void foo(bool a ...);
Beliebig viele Argumente vom Type bool (mindestens eins), <cstdarg> um an die Argumente zu kommen.

02-11-2006 21.54

Klassen und Objekte

So wird eine Klasse definiert
class MyClass : public MyParentClass
{
public:
MyClass();
MyClass(int x);
int my_method() const;
static int getTime();

protected:
int my_var;

private:
};

MyClass::MyClass() {
..
}

int MyClass::MyClass(int x) : MyParentClass(59)
{
...
}

int MyClass::my_method() const {
...
}
int MyClass::getTime() {
...
//in java super()
this is super() for c++
MyParentClass::getTime();
...
}

Das ist der einfachste Weg eine Instanz einer Klasse anzulegen
MyClass   x(5);
Hier wird ein Zeiger auf die neue Instanz zurückgegeben. Man muss selbst mit delete den Speicher wieder freigeben, wenn man die Instanz nicht mehr benötigt.
MyClass * x=new MyClass(5);
...
delete x;
Hierbei wird erst eine Instanz der Klasse mit dem Default Konstruktor erzeugt und direkt danach wieder überschrieben mit einer Instanz, die über einen eigenen Konstruktor erzeugt wurde
MyClass   x;
x=MyClass(42);
Das sollte man natürlich vermeiden. Das ist besonders dann ein Problem, wenn eine Klasse als Attribute auf andere Klassen verweist. Z.B. hat die Klasse Planet hier ein Objekt des Typs Vector3D
class Planet
{
public:
Planet(const double pMass, const double x, const double y, const double z);

private:
double mass;
Vector3D position;
};
Man könnte jetzt solch einen Konstruktor definieren
Planet::Planet(const double pMass, const double x, const double y, const double z)
{
position=Vector3D(x,y,z);
}
Aber dann würde direkt vor dem Aufruf des Konstruktors erst mal position über einen Aufruf des Default Konstruktors erzeugt, die dann von uns sofort wieder überschrieben würde.
So kann man das verhindern und position gleich mit sinnvollen Parametern initialisieren
Planet::Planet(const double pMass, const double pRadius, const double x, const double y, const double z) : position(x,y,z)
{
...

Normalerweise definiert Klassen in C++ so, dass man in einer .h Datei die Schnittstellen der Klasse und getrennt davon die eigentliche Implementierung. Hier ein Beispiel einer Vektor Klasse
Vector3D.h
#ifndef _Vector3D_h_
#define _Vector3D_h_

#include <math.h>
#include <iostream>

class Vector3D
{
public:
// constructor
Vector3D(const double pX, const double pY, const double pZ);

// get the X value
double getX() const;

// return a new Vector3D which is the sum of us and the other
Vector3D add(const Vector3D &other) const;

// the length of ourself
double length() const;

private:
double x,y,z;
};


// overwrite the << operator
std::ostream& operator<<(std::ostream &strm, const Vector3D &a);

#endif


Vector3D.cpp
Vector3D::Vector3D(const double pX, const double pY, const double pZ)
{
x=pX;
y=pY;
z=pZ;
}

double Vector3D::getX() const
{
return x;
}

double Vector3D::length() const
{
return sqrt(x*x+y*y+z*z);
}

std::ostream& operator<<(std::ostream &strm, const Vector3D &a)
{
return strm << "X Value is " << a.getX();
}


Statische Methoden und Variablen

Achtung, man braucht genau eine zusätzliche Definition der statischen Variablen außerhalb der Klasse.
Wenn man eine statische Methode implementiert nicht das Schlüsselwort static wiederholen.
class MyOtherClass {

public:

MyOtherClass() {
z=0;
}

static int foo() {
return ( MyOtherClass::x + MyOtherClass::y );
}

int bar() {
return ( MyOtherClass::x + MyOtherClass::y + this->z );
}

protected:
static int y;
static int x;
int z;
};

// !!!
int MyOtherClass::x=0;
int MyOtherClass::y=0;
// !!!

int main() {
...
}

Virtuelle Methoden

Wenn man nicht virtuelle Methoden in abgeleiteten Klassen modifiziert ist man vielleicht überrascht dass hier die Methode der Vaterklasse aufgerufen wird. Übel wenn der Destruktor so eine Methode ist.
#include <iostream>
using namespace std;

class A {
public:
int foo() {
return 1;
}

virtual int bar() {
return 1;
}

//~A() { <------ Don't!
virtual ~A() {
//...
}
};

class B : public A {
public:
int foo() {
return 2;
}

virtual int bar() {
return 2;
}

//~B() {
virtual ~B() {
//..
}
};

int main() {
A a; B b; A * x; x=&b;
cout << a.foo() << a.bar() << endl; // 11
cout << b.foo() << b.bar() << endl; // 22
cout << x->foo() << x->bar() << endl; // 12
return 0;
}

Zugriffskontrolle

public:Alle
protected:Alle Objekte der Klasse + deren Erben
private:Nur Objekte dieser Klasse
Klassenvariablen wann immer möglich private, methoden protected, möglichst wenig public.

Namespace

Sehr sinnvoll verschiedenen Programmteile in verschiedene Namespaces zu packen.
namespace Foo {
double x;
double getX();
}

namespace Foo_1 {...};
namespace Foo_2 {...};

namespace MyFoo=Foo_2;
Wie man sieht kann man sogar einen Namespace unter einem anderen Namen erreichen und so schnell zwischen zwei Namespaces wechseln.

So kann man auf Elemente aus einem Namespace zugreifen
int y=Foo::x;

using Foo::getX;
using Foo;
Mit using kann man auch als Erbe eine Methode des Vaters, die dort protected ist, als public Methode von uns selbst anbieten
class A {
protected:
double getX();
};

class B : public A {
public:
using A::getX;
};

Copy Konstruktor

Foo a;
Foo b=a;
ruft den Copy Konstruktor auf:
Foo::Foo(const Foo& a);
Wenn das Objekt Zeiger auf Speicher hält den es im Destruktor wieder freigeben muss kann das zu Problem führen:
Foo a;
Foo b=a;
Foo c;
Hier wird nur für a und c der Konstruktor aufgerufen, für b dagegen der Copy Konstruktor.
Am Ende wird allerdings für alle drei der Destruktor gestartet und so unter Umständen Speicher doppelt freigegeben.

Wenn es einen passenden Konstruktor gibt kann man Objekte auch durch Zuweisungen initialisieren:
class complex {
public:
complex(double r, double i) {this->re=r; this->im=i;}
complex(double r) {this->re=r; this->im=0;}
private:
double re,im;
}

complex a=complex(2.3);
complex b=2.3;

Vererbung

Mehrfaches Erben

class A                      {};
class B : public A {};
class C : public A {};
class D : public B, public C {};

A A
| |
B C
\ /
D
Jetzt gibt es 2 verschiedene Objekte A, was vermutlich nicht gewünscht war. Besser A als virtuelle Basisklasse benutzen:
class A                      {};
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

A
/ \
B C
\ /
D

Casten

FOO a;
BAR b=(BAR)a;

FOO * c;
BAR * d=(BAR *)c;

dynamic_cast / static_cast

Angenommen man hat folgende Hierarchie
class A                      {};
class B {};
class C : public A {};
class D : public B {};
class E : public C, public D {};

A B
| |
C D
\ /
E
Und möchte ein Objekt vom Typ A* in eines vom Typ B* konvertieren.
Dann kann man das entweder händisch konvertieren:
A * a;
C * c=(C*) a;
E * e=(E*)c;
D * d=e;
B * b=d;
Oder mit dynamic_cast
B * b=dynamic_cast<B*>a;
Falls A nicht mindestens eine virtual Methode hat wird dieser Fehler erscheinen:
error: cannot dynamic_cast `a' (of type `class A*') to type `class B* ' (source type is not polymorphic)
static_cast ist etwas schneller, überprüft aber nicht was es macht. Vermeiden.

typeid

Foo a;
Bar b;
if (typeid(a)==typeid(FOO)) ...
if (typeid(a)==typeid(BAR)) ...
if (typeid(b)==typeid(FOO)) ...
if (typeid(b)==typeid(BAR)) ...

28-09-2014 16.40

Exceptions

try {
...
throw(5);
...
throw(5.0);
}
// default catch, catch all
catch(int a)
{
}
catch(float b)
{
}
catch(...)
{
}
Wenn möglich angeben welche Exceptions von einer Methode geworfen werden können:
void foo() throw();
void foo() throw(X,Y);
Im Falle eines Verstoßes dagegen wird unexpected aufgerufen.

auto_ptr

Problem:
foo() {
...
fopen(FILE);
...
if (...) throw error1;
...
fclose(FILE);
...
}
Wenn eine Exception geworfen wird muss die Datei noch geschlossen werden. Das für alle denkbaren Fälle zu behandeln kann anstrengend werden. Schöne Lösung, eine Klasse schreiben die im Konstruktor die Datei öffnet und sie im Destruktor schließt:
class X {
public:
X() { fopen(FILE); };
~X() { fclose(FILE); };
}
Jetzt kann man obiges Beispiel wie folgt umschreiben:
foo() {
...
X x;
...
if(...) throw error1;
...
}
Jetzt wird die Datei geschlossen sobald das Objekt nicht mehr im Scope ist (also auch wenn die Exception geworfen wird).
Ein generisches Objekt dass dazu benutzt werden kann ist auto_ptr aus <memory>.

Exception muss kein Fehler sein

Stack<int> data;

try
{
for(;;) { data.pop(); }
}
catch(data::Empty)
{
...
}
Statt Abbruchbedingungen in der for Schleife Exception fangen. Das ist aber meistens schwieriger zu lesen, vermeiden.

Assertions

//#define NDEBUG
#include <cassert>
assert(a>0 && b<0);
14-11-2006 20.01

Templates

Klassentemplates

template <class Pairtype>
class Pair
{

template class Pair<int>;
template class Pair<double>;

public:
Pair();
Pair(Pairtype left, Pairtype right);
Pairtype get_left();
void set_left(Pairtype left);

protected:
Pairtype left;
Pairtype right;
};
}
template <class Pairtype>
Pair<Pairtype>::Pair(Pairtype left, Pairtype right)
{
this->left=left;
this->right=right;
}

template <class Pairtype>
Pairtype Pair<Pairtype>::get_left()
{
return left;
}

template <class Pairtype>
void Pair<Pairtype>::set_left(Pairtype left)
{
this->left=left;
}
Pairtype<int> foo;
07-09-2006 14.27

Singleton

class tg {
public:
static tg& get_instance(void) {
return tg::my_instance;
}
private:
static tg my_instance;
tg() {

}

tg(const tg&);

tg& operator=(tg&);
};

tg tg::my_instance;

int main(int argc, char **argv)
{
tg& a = tg::get_instance();
}
06-08-2005 13.09

Operator

class MyClass {
public:
...
MyClass & operator+=(const MyClass & other);
}

friend bool operator< (const MyClass& a, const MyClass& b) {
return a.foo<b.foo;
}

std::ostream& operator<< (std::ostream& os, const MyClass& a) {
os << "Foo: " << a.foo << " Bar: " << a.bar << std::endl;
return os;
}

Inkrement / Dekrement Operator

// ++i
MyInt MyInt::operator++() {
value+=1;
return value;
}

// i++
MyInt MyInt::operator++(int) {
MyInt rescue=value;
value+=1;
return rescue;
}
26-10-2006 03.58

Iteratoren

vector <ContainerType>                           a;
vector <ContainerType>::iterator iter_a;
vector <ContainerType>::const_iterator constiter_b;
vector <ContainerType> c;
vector <ContainerType>::reverse_iterator inter_r;

iter_b=c.end();
iter_a=c.begin();
(*iter_a)=c.at(0);
while (iter_a!=c.end())
{
cout << *iter_a;
iter_a++;
}

if (c.end()!=c.begin()) {
iter_a=c.end();
do {
iter_a--;
cout << *iter_a << endl;
} while (iter_a!=c.begin());
}

for (iter_r=c.rbegin(); iter_r!=c.rend(); iter_r++) {
cout << *iter_r << endl;
}

for(iter_a=c.begin(),x=0;
iter_a!=c.end() && x<100;
iter_a++, x++
) {}

back_insert_iterator

Wenn man etwas in eine Datenstruktur einfügen möchte braucht man einen passenden Iterator.
b.end() ist zeigt hinter das Ende der Datenstruktur, b.begin() würde Elemente überschreiben.
Lösung: back_insert_iterator
#include <vector>
#include <list>
#include <iostream>

using namespace std;
int main() {

vector<int> a;
vector<int> b;

a.push_back(1);
a.push_back(2);
a.push_back(3);
copy(a.begin(), a.end(), back_insert_iterator<vector< int > >(b));

// 1
// 2
// 3
for(vector<int>::const_iterator i=b.begin(); i!=b.end(); i++)
cout << *i << endl;
cout << endl;

list<int> myList;
back_insert_iterator< list< int > > myListIteratorBack(myList);
front_insert_iterator< list< int > > myListIteratorFront(myList);

myListIteratorBack=4;
myListIteratorBack=5;
myListIteratorFront=6;

// 6
// 4
// 5
for(list<int>::const_iterator i=myList.begin(); i!=myList.end(); i++)
cout << *i << endl;
cout << endl;

}

Funktionen die über eine stl Datenstruktur iterieren

Statt selbst über Datenstruktur zu iterieren:
vector<int> a;
for(vector<int>::iterator i=a.begin(); i!=a.end(); i++) foo(*i);
besser vorhanden stl Algorithmen benutzen:
sort(a.begin(), a.end());
for_each(a.begin(), a.end(), foo);
Letzteres geht aber erst mal nur mit Funktionen, nicht über Methoden. Wenn man jetzt eine Klasse hat:
class X {
public:
void draw();
}
und für jedes Element eine Methoden aufrufen will muss man die über eine Funktion aufrufen:
vector<X *> data;

void foo(X * x) {
x->draw();
}

for_each(data.begin(), data.end(), foo);
Es gibt schon eine fertige Funktion mem_fun die das leistet:
for_each(data.begin(), data.end(), mem_fun(&X::draw));
Liste nützlicher Funktionen:

Sortieren mit einem Funktor

functor
class mycomp {
public:
bool operator()(double x, double y) { return fabs(x) < fabs(y); }
};

vector<double> data;
sort(data.begin(), data.end(), mycomp());
30-10-2006 21.48

Kontrollstrukturen

continue

Überspringt den Rest des aktuellen Schleifendurchlaufs und macht mit dem nächsten weiter.
for (int i=0; i<5; i++) {
std::cout << "\n";
std::cout << i << " ";
if (i==2) continue;
std::cout << i;
}
Ausgabe
0 0
1 1
2
3 3
4 4

break

Bricht die Schleife komplett ab
for (int i=0; i<5; i++) {
std::cout << "\n";
std::cout << i << " ";
if (i==2) break;
std::cout << i;
}
Ausgabe
0 0
1 1
2
Statt continue und break gibt es auch noch goto! ;-)
goto foo;
...
foo:
...

Conditional Expressions

if  (a<=b)   max=a; else max=b;
max=(a<=b) ? a : b;



21-09-2014 15.22

Zwei verschiedene C++ Compiler für ein Projekt benutzen


Gegeben folgendes kleines Projekt:
new.cpp
#include "old.h"

class newClass
{
public:
int newClassMethod(int x) {
return proxy(x);
}

};

int main(int argc, char **argv) {
newClass newObject;
return newObject.newClassMethod(5);
}
old.h
int proxy(int x);
old.cpp
#include "old.h"

class oldClass
{
public:
int myold(int x) {
return 25;
}
};

int proxy(int x) {
oldClass oldObject;
return oldObject.myold(x);
}

In new.cpp wird ein Objekt der Klasse newClass angelegt welche Code aus der Datei old.cpp aufruft. Dieses Projekt sollte ohne Probleme compilieren:
g++-2.95 -c old.cpp   -o old.o
g++-2.95 -c new.cpp -o new.o
g++-2.95 old.o new.o -o myproject

Spannend wird es wenn old.cpp mit g++-2.95 compiliert wurde und man jetzt gerne für alle anderen Dateien außer old.cpp (z.B. weil man keinen Zugang zu den Quellen von old.o um es neu zu kompilieren) einen neueren Compiler nutzen möchte
g++-2.95 -c old.cpp   -o old.o
g++-3.3 -c new.cpp -o new.o
g++-3.3 old.o new.o -o myproject

Das wird fehlschlagen
new.o(.gnu.linkonce.t._ZN8newClass14newClassMethodEi+0xd): In function `newClass::newClassMethod(int)':
: undefined reference to `proxy(int)'
collect2: ld returned 1 exit status
weil sich die ABI zwischen gcc-3.2 und seinen Vorgängerversionen unterscheidet. Mit einem kleinen Trick klappt es aber doch. Da sich die ABI nur für C++ Code geändert hat genügt es einfach eine Funktion als C Funktion zu deklarieren und sie ebenfalls mit dem alten Compiler compilieren. Dann kann sie als Schnittstelle zwischen beiden Welten dienen. In diesem Beispiel muss man nur diese kleine Änderung vornehmen:
old.h
extern "C"
{
int proxy(int x);
}
Und schon kann man sie als Schnittstellen zwischen beiden Welten nutzen.

Probleme gibt es aber wenn sowohl neue als auch alte Dateien z.B. die stl benutzen wollen. Aber auch dafür scheint es eine Lösung zu geben:
Beispielsprojekt: newClass.cpp, newClass.h, new.cpp , oldClass.cpp , oldClass.h, old.cpp, old.h.

Als erstes alle Dateien mit dem jeweiligen Compiler compilieren
g++-2.95 -c old.cpp      -o old.o
g++-2.95 -c oldClass.cpp -o oldClass.o
g++-3.3 -c new.cpp -o new.o
g++-3.3 -c newClass.cpp -o newClass.o
Jetzt alle alten .o Dateien statisch zu einer neuen .o zusammenfassen. Wie das geht kann man dem Befehl:
g++-2.95 -v -static -r -o allOldStuff.o   old.o oldClass.o
entnehmen. Allerdings würden dabei im nächsten Schritt einige .o Dateien doppelt eingelesen. Lässt man diese weg sieht es ungefähr so aus:
/usr/lib/gcc-lib/i386-linux/2.95.4/collect2 -m elf_i386 -static -o allOldStuff.o -r -L/usr/lib/gcc-lib/i386-linux/2.95.4 old.o oldClass.o -lstdc++ -lm -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-linux/2.95.4/crtend.o /usr/lib/crtn.o
Jetzt kann man alle neuen .o Dateien gegen die zusammengefasste alte .o Datei linken:
g++ -o MyProgram  new.o newClass.o allOldStuff.o

Fertig. :-)

Leider scheint es in diesem Fall zur Laufzeit Probleme zu geben.
06-09-2006 00.01
Danke an Richard Eckart für die vielen Hinweise!
18-05-2008 15.06
Powered by PHP Created with Xemacs Valid XHTML 1.0! Valid CSS!