31
Iul
2007

Acest articol se adresaza programatorilor de C++ care vin dinspre C si nu sunt inca familiarizati pe deplin cu limbajul. Desi C++ mosteneste cea mai mare parte a limbajului C, sunt aspecte care trebuie tratae cu atentie, precum folosirea lui new/delete in detrimentul lui malloc/free, casturile C++ in defavoarea celor in stil C, sau folosirea containerelor STL in locul array-urilor alocate static sau dinamic. Acest articol explica de ce este de preferat std::string lui char*.

Evita declaratiile incorecte a sirurilor

Atunci cand vor sa foloseasca un string, multi programatori fac astfel:

char* nume = "marius";

Aceasta poate parea ok la o prima vedere, dar atunci cand cineva incearca sa modifice un element a lui nume este aruncata o exceptie:

nume[0] = 'M';

"marius" este un string literal, iar nume este unpointer care indica spre acesta. Tentadiva de modificare a literalelor reprezinta comportament nedefinit. In mod normal (de exemplu cu VC++ 2005) se genereaza o eroare la run-time. Declaratie corecta ar fi:

const char* nume = "marius";

In acest caz in compilatorul va fi cel care va genera o eroare in cazul tentativei de modificare a lui nume.

Abordarea in stil C

Ce se poate face este declararea unui sir de caractere de lungime fixa.

char nume[] = "marius";
nume[0] = 'M';

In acest caz nume este un sir de 7 charactere, terminate cu null, si care este initializat cu string-ul literal "marius". Cu alte cuvinte valoarea literalului este copiata in acest sir, care poate fi modificat in voie.

Daca doriti sa adaugati un alt string la acesta puteti folosi strcat:

char nume[] = "marius";
strcat(nume, " bancila");

In momentul rularii programului o eroare va fi generate, intrucat sirul nume nu este suficient de mare pentru a tine un string atat de lung, si memoria va fi scrisa dincolo de limitele acestui sir. Desigur se poate aloca un sir suficient de mare care sa acomodeze ambele literale:

char nume[50] = "marius";
strcat(nume, " bancila");

Cu toate acestea se poate intampla in orice moment sa apara un string mai mare decat buffer-ul alocat (de exemplu in cazul numelui Carlos Marìa Eduardo García de la Cal Fernàndez Leal Luna Delgado Galván Sanz ) Pe langa aceasta, abordarea are dezavantajul ca poate consuma in mod inutil memorie. Daca declarati nume ca un buffer de 100 de elemente, din care in medie sunt folosite 20, pentru 100000 de nume se folosesc inutil 8000000 octeti. Ceea ce ne conduce la o noua abordare.

Alocarea dinamica a memoriei

Urmatorul pas ar fi alocarea dinamica:

char* nume = new char[strlen("marius")+1];
strcpy(nume, "marius");

In acest caz se poate realoca oricata memorie este necesara:

char* temp = new char[strlen(nume) + strlen(" bancila") + 1];
strcpy(temp, nume);
strcat(temp, " bancila");

delete [] nume;
nume = temp;

Problema e ca trebuie scris mult cod, pentru care trebuie facuta probabil si mentenanta.

Asigura managementul corect al memoriei in clase

Sa consideram cazul unei clase Persoana care ar trebui printre altele sa stocheze numele unei persoane. Un prim inpuls ar fi putea duce la urmatoarea clasa:

class Persoana
{
   char* nume;
};

Aparent e in regula, dar de fapt pentru aceasta clasa trebuie asigurat:

  • Un constructor care ia un string pentru initializarea numelui
  • Un constructor de copiere custom care sa asigure o copiere in profunzime (deep copy) a obiectului, in locul celei default oferita de compilator (shallow copy)
  • Un operator= custom
  • Un destructor care sa elibereze memoria alocata dinamic.

Astfel incat clasa Persoana ar trebuie de fapt sa arata astfel:

class Persoana
{
   char* nume;
public:
   Persoana(const char* str)
   {
      nume = new char [strlen(str)+1];
      strcpy(nume, str);
   }

   Persoana(const Persoana& p)
   {
      nume = new char [strlen(p.nume)+1];
   }

   Persoana& operator=(const Person& p)
   {
      if(this != &p)
      {
         delete [] nume;

         nume = new char [strlen(p.nume)+1];
         strcpy(nume, p.nume);
      }

      return *this;
   }

   ~Persoana()
   {
      delete [] nume;
   }
};