O problema de header

Intrebari despre limbajul C++, standardul C++, STL, OOP in C++ sau alte subiecte nelegate de VisualC++
Post Reply
Abi_Moonbow
Junior
Junior
Posts: 15
Joined: 17 Apr 2013, 11:36
Judet: Argeş

O problema de header

Post by Abi_Moonbow » 27 Apr 2013, 21:01

Salutare!
As vrea sa va intreb in legatura cu header files... Imi creez clasa separat de main, intr-un header specific, iar metodele in afara clasei, dar in acelas header. Care e diferenta dintre cum procedez eu si daca as implementa definitia clasei intr-un header, iar metodele intr-un .cpp separat? Care metoda e mai avantajoasa?

Multumesc!
Last edited by Abi_Moonbow on 30 Apr 2013, 19:02, edited 1 time in total.



User avatar
bu7ch3r
Membru++
Membru++
Posts: 326
Joined: 17 May 2011, 15:17
Judet: Iaşi
Location: Sofia
Contact:

Re: O problema de header

Post by bu7ch3r » 28 Apr 2013, 00:02

Pai ia include headerul ala in mai multe fisiere din aceeasi solutie, o sa vezi atunci :)
Cu stima,
Lupu Claudiu

User avatar
Silviu Ardelean
Senior
Senior
Posts: 1175
Joined: 12 Jul 2007, 09:22
Judet: Timiş
Location: Timisoara
Contact:

Re: O problema de header

Post by Silviu Ardelean » 28 Apr 2013, 00:15

Declari si implementezi in fisierul .h cand scri metode cu template-uri sau cand vrei sa ai metode inline. Legat de metodele inline, indicat e sa fie de doar cateva linii. Chiar si atunci nu ai garantia ca, compilatorul iti va genera codul cu adevarat inline.

Altfel, in conditii normale implementarea o pui in .cpp.

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: O problema de header

Post by Ovidiu Cucu » 28 Apr 2013, 11:57

Sa zicem ca nu folosim headere si punem definitia unei clase direct intr-un fisier B.cpp.
Exemplu #1

Code: Select all

// B.cpp

class A
{
public:
   A();
};

void f1()
{
   A a;
}
Pana aici toate bune si frumoase, insa in programe mai complexe se poate ivi necesitatea folosirii clasei A si in alte fisiere sursa.
Mergand pe acelasi principiu, ar trebui scris ceva de genul:
Exemplu #2

Code: Select all

// C.cpp
class A
{
public:
   A();
};

void f2()
{
   A a;
}
Adica, am scris definita clasei A identic si la fel in doua fisiere diferite ceea ce, este "peste mana". Ok, Ctrl+C/Ctrl+V este la indemana, insa asa ceva nu este practic. Ce facem daca am folosit-o in N locuri si trebuie sa modificam A? Modificam in N locuri? Daca apar greseli/diferente?
NU, asa ceva nu este nici pe departe acceptabil!

Asa ca, luam frumusel definitia clasei A si o punem intr-un fisier header:
Exemplu #3

Code: Select all

// A.h - definitia clasei A
#ifndef A_H
#define A_H

class A
{
public:
   A();
};

#endif // A_H
Mai departe, pentru a folosi clasa A, nu trebuie decat sa includ in A.h in B.cpp, C.cpp si in orice alt fisier sursa am nevoie:
Exemplu #4

Code: Select all

// B.cpp
#include "A.h"

void f1()
{
   A a;
}
Exemplu #5

Code: Select all

// C.cpp
#include "A.h"

void f2()
{
   A a;
}
Pentru a fi mai clar ce se intampla mai departe si a lamuri si de ce trebuie ca implementarea (functiilor) clasei A sa fie intr-un cpp si nu in header, deschid o mica paranteza.
Traducerea unui program de la codul sursa pana la executabil se face in doua faze mari si late (pe scurt):
1. Compilarea in care un program numit compilator traduce codul sursa in cod masina. In urma compilarii, rezulta fisiere intermediare numite module obiect. La compilatorul de la MSVC, acestea au extensia .obj.
2. Editarea de legaturi in care un program numit linker ia aceste module obiect (impreuna cu alte module externe) si genereaza un fisier executabil (.exe, .scr, etc) sau o biblioteca (.dll, .lib, etc).

Compilarea cuprinde la randul ei doua faze:
1.1 Preprocesarea (faza preprocesor), in care compiatorul ia fiecare fisier sursa CPP (sau C, sau alte fisiere recunoscute de compilator) si rezolva, printre altele directivele preprocesor (printre care acele directive #include). In urma preprocesarii rezulta surse intermediare numite translation units.
1.2. Compilarea propriuzisa, in care compilatorul ia fiecare translation unit si il transforma in modul obiect.

Luand cazul discutat, din sursele #4 si #5 resulta translation unit-uri de forma #1 si #2 (in B.cpp si C.cpp s-a inlocuit #include "A.h" cu continutul headerului A.h).

Acuma, sa spunem ca in A.h punem si implementarea (am figurat doar un constructor, pentru simplitatea exemplului).
Exemplu #6

Code: Select all

// A.h - definitia + implementarea clasei A
#ifndef A_H
#define A_H

class A
{
public:
   A();
};

A::A()
{
   // ...
}

#endif // A_H
In urma preprocesarii, din B.cpp si C.cpp rezulta urmatoarele translation units
Exemplu #7

Code: Select all

class A
{
public:
   A();
};

A::A()
{
   // ...   
}

void f1()
{
   A a; 
}
respectiv
Exemplu #8

Code: Select all

class A
{
public:
   A();
};

A::A()
{
   // ...   
}

void f2()
{
   A a; 
}

iar in final, dupa compilarea prorpiuzisa, rezulta doua module obiect A.obj si B.obj continand implementarea aceleeasi functii A::A.
Mai departe, vine linkerul si vrea sa puna la un loc A.obj si B.obj, cand surprize surprize... gaseste in ambele implementarea aceleiasi functii.
Pe care s-o aleaga? Bine-bine, sa zicem ca in exemplul dat n-ar conta, fiind implementari identice, insa linkerul nu are de unde sa stie si nu-i traba lui.
Asa ca... "scuipa" ceva de genul:

Code: Select all

1>Linking...
1>C.obj : error LNK2005: "public: __thiscall A::A(void)" (??0A@@QAE@XZ) already defined in B.obj
1>C:\Projects\Demo\Debug\demo.exe : fatal error LNK1169: one or more multiply defined symbols found
E clar pana aici?
Sper ca da, asa ca hai sa facem cum trebuie facut, adica sa mutam implementarea (functiilor) clasei A din A.h in A.cpp.
Exemplu #9

Code: Select all

// A.cpp - implementarea clasei A
#include "A.h"

A::A()
{
   //...
}
Dupa preprocesare, resulta din A.cpp, B.cpp si respectiv C.cpp urmatoarele translation units
Exemplu #10

Code: Select all

class A
{
public:
   A();
};

A::A()
{
   // ...   
}
Exemplu #11

Code: Select all

class A
{
public:
   A();
};

void f1()
{
   A a; 
}
Exemplu #12

Code: Select all

class A
{
public:
   A();
};

void f2()
{
   A a; 
}
resultand in urma compilarii A.obj, B.obj si C.obj in care implementarea A::A este prezenta o singura data si anume in A.obj.
De-acuma, linker-ul nu mai vede nimic ambiguu si e fericit... ca si programmerul, de altfel. :)

De la cele discutate mai sus, fac exceptie functiile inline, care trebuie implementate intr-un loc (intr-un header, intr-un alt fisier .inl, inclus in header), in care sa fie "vizibile" in locurile unde sunt folosite.
La fel si atunci cand lucram cu templates.
De ce asa cu astea si nu altfel, poate fi subiectul unei alte discutii.

[ off-topic ]
Probabil ca voi fi acuzat din nou ca "scriu romane" in forum. ;)
Zic totusi ca, unele chestii trebuie lamurite in detaliu. Pornind de la cazul de fata, parerea mea e ca ceea ce am spus mai sus ar trebui predat la primele lectii de C sau C++.
Cu multi ani in urma, asa se proceda de exemplu la cursul de... FORTRAN.
Acuma insa, se pare ca profii sau nu stiu sau nu vor sau considera ca fazele translatarii unui program sunt de la sine intelese.
Cel putin ca "sunt de la sine intelese" am vazut in multe ocazii cu ochiul liber, inclusiv la programatori cu pretentii. :D

Abi_Moonbow
Junior
Junior
Posts: 15
Joined: 17 Apr 2013, 11:36
Judet: Argeş

Re: O problema de header

Post by Abi_Moonbow » 28 Apr 2013, 15:19

Da, va multumesc din nou pentru raspunsuriile voastre, pentru ca aduceti claritate in mintea mea.

PS: Ovidiu Cucu iti multumesc inca odata pentru "romanele" pe care mi le scrii ca raspunsuri! Imi place sa inteleg tot mecanismul nu numai principiul.

Post Reply