PDA

Visualizza la versione completa : [C++] Chiarimento su costruttore oggetto


MrX87
19-05-2014, 23:09
Ciao a tutti, volevo un semplice chiarimento sull'implementazione di un semplice costruttore in c++, scrivo sotto un esempio:

objectA.j


class ObjectA {
public:
ObjectA(string mystr);
~ObjectA();
private:
string mystr;
}


objectB.h


class ObjectB {
public:
ObjectB();
~ObjectB();
private:
ObjectA a1;
}


objectB.cpp


ObjectB::ObjectB() {
a1 = ObjectA("Mario"); // è una operazione valida come chiamata al costruttore???
}

MItaly
20-05-2014, 00:05
No. Se vuoi richiamare un costruttore (di campi o di classi base) da un costruttore devi usare la sintassi della lista di inizializzazione (http://www.learncpp.com/cpp-tutorial/101-constructor-initialization-lists/). Quello che stai facendo lì è creare un ObjectA temporaneo e assegnarlo ad a1 tramite operatore di assegnamento.

(per inciso, quel codice non potrebbe compilare, dato che ObjectA non ha costruttore di default, e non ne richiami il costruttore parametrico)

MrX87
20-05-2014, 08:29
Grazie per la risposta e la correzione, comunque se dichiarassi invece:



private:
ObjectA *a1;


A questo punto lo inizializzo con la new:



a1 = new Object("Mario");


dovrebbe funzionare...e ovviamente dovrei prevedere una delete a1 nel distruttore di ObjectB giusto?

MItaly
20-05-2014, 13:40
Sì, ma se inizi ad avere oggetti tenuti dentro per puntatore devi pensare ad una semantica di copia sensata, o quantomeno disabilitare costruttore di copia e operatore di assegnamento, altrimenti con il costruttore di copie di default ti ritrovi più ObjectB che puntano allo stesso a1 e cercano tutti di distruggerlo alla loro distruzione (con risultati catastrofici).
In generale, se possibile è più semplice avere membri "veri" e non puntatori a roba allocata sull'heap.

MrX87
21-05-2014, 10:20
Sì, ma se inizi ad avere oggetti tenuti dentro per puntatore devi pensare ad una semantica di copia sensata, o quantomeno disabilitare costruttore di copia e operatore di assegnamento, altrimenti con il costruttore di copie di default ti ritrovi più ObjectB che puntano allo stesso a1 e cercano tutti di distruggerlo alla loro distruzione (con risultati catastrofici).
In generale, se possibile è più semplice avere membri "veri" e non puntatori a roba allocata sull'heap.

Suppongo che quello che hai appena spiegato possa accadere in una situazione del genere se non ho capito male:
ObjectA


#ifndef OBJECTA_H
#define OBJECTA_H

class ObjectA
{
public:
int y;
ObjectA();
~ObjectA();
};
#endif // OBJECTA_H


ObjectA.cpp


#include <iostream>
#include "ObjectA.h"

ObjectA::ObjectA()
{
y = 10;
std::cout << "Costruttore A " << std::endl;
}

ObjectA::~ObjectA()
{
}


ObjectV


#ifndef OBJECTV_H
#define OBJECTV_H

#include "ObjectA.h"

class ObjectV
{
public:
int x;
ObjectV();
~ObjectV();
ObjectA* getObjA();


private:
ObjectA* a1;
};


#endif // OBJECTV_H


ObjectV.cpp


#include <iostream>
#include "ObjectV.h"

ObjectV::ObjectV()
{
std::cout << "Costruttore V " << std::endl;
a1 = new ObjectA();
}

ObjectV::~ObjectV()
{
delete a1;
}


ObjectA* ObjectV::getObjA() {
return a1;
}


main


#include <iostream>
#include "ObjectA.h"
#include "ObjectV.h"

int main (void) {


ObjectV v1;
ObjectV v2 = v1;


ObjectA* av1 = v1.getObjA();
std::cout << "address a->y = " << av1->y << std::endl;
delete av1;


ObjectA* av2 = v2.getObjA();
std::cout << "address a->y = " << av2->y << std::endl; // in questo caso si av2 non esiste più poichè distrutto dalla delete di av1.


getchar();
return 0;
}



anche se un po estremizzata come situazione però il concetto penso è questo....

MItaly
21-05-2014, 12:17
No, il concetto è molto più semplice.


int main()
{
ObjectV v1;
ObjectV v2(v1);
return 0;
}

Crasha (se sei fortunato) perché sia il distruttore di v1 che quello di v2 stanno cercando di eliminare lo stesso oggetto (quello a cui puntano entrambi gli a1).

MrX87
21-05-2014, 16:30
No, il concetto è molto più semplice.


int main()
{
ObjectV v1;
ObjectV v2(v1);
return 0;
}

Crasha (se sei fortunato) perché sia il distruttore di v1 che quello di v2 stanno cercando di eliminare lo stesso oggetto (quello a cui puntano entrambi gli a1).

Okay....mentre se implementassi opportunamente i costruttori di copia in questo modo:



ObjectV::ObjectV(const ObjectV& objV )
{
std::cout << "Costruttore copia V " << std::endl;
this->x = objV.x;
this->a1 = new ObjectA(objV.a1);
}


ObjectA::ObjectA(const ObjectA* objA)
{
std::cout << "Costruttore copia A " << std::endl;
this->y = objA->y;
}


dovrei risolvere il problema, infatti se nel main stampo i due membri y dell'oggetto ObjectA ci sono valori differenti.

MItaly
21-05-2014, 18:47
Non solo, devi implementare anche l'operatore di assegnazione, altrimenti questo:


int main()
{
ObjectV v1;
ObjectV v2;
v2=v1;
return 0;
}
genera un memory leak e una double free.

Di nuovo: in genere è molto meglio avere solo membri allocati dentro la classe stessa piuttosto che roba allocata nell'heap, oppure aggirare il problema disabilitando costruttore di copia e operatore di assegnazione.

MrX87
21-05-2014, 19:22
Non solo, devi implementare anche l'operatore di assegnazione, altrimenti questo:


int main()
{
ObjectV v1;
ObjectV v2;
v2=v1;
return 0;
}
genera un memory leak e una double free.

Di nuovo: in genere è molto meglio avere solo membri allocati dentro la classe stessa piuttosto che roba allocata nell'heap, oppure aggirare il problema disabilitando costruttore di copia e operatore di assegnazione.

Okay vero, mi era sfuggito. Comunque seguirò il consiglio di avere solo membri statici dentro la classe, ma era solo per capire meglio il funzionamento delle cose e avere più chiaro il comportamento del codice che scrivo.

Grazie ancora delle spiegazioni

MItaly
22-05-2014, 00:57
Okay vero, mi era sfuggito. Comunque seguirò il consiglio di avere solo membri statici dentro la classe,
Occhio che i campi static sono un'altra cosa...

ma era solo per capire meglio il funzionamento delle cose e avere più chiaro il comportamento del codice che scrivo.
Consiglio: ci sono dei casi in cui è necessario tenere dei puntatori nella classe, o comunque dover gestire delle risorse; in questi casi:

se esistono classi che già fanno quello che vuoi fare incapsulando la gestione della memoria, usa quelle; ovvero, non usare vettori allocati con la new, ma std::vector;
se invece hai puntatori ad oggetti di cui la classe è proprietaria, valuta l'uso di smart pointer, ed eventualmente disabilita copia e assegnazione (dichiarandoli come private e non definendoli, cosa che comunque viene da sé se hai come membri degli oggetti non copiabili) se non è necessaria;
se devi gestire risorse (connessioni a DB, handle a risorse, ...), crea una classe wrapper che si occupi solo di quello (e.g. una classe DBConnection), non fare gestire più risorse direttamente alla stessa classe;
nella classe che gestisce direttamente le risorse, se ha senso ed è ben definita una semantica di copia, definisci sempre assieme i "big three" (costruttore di copia, operatore di assegnamento, distruttore); in tal caso, usa il copy and swap idiom (http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) invece di inventarti cose strane, è uno dei pochi metodi che sicuramente funziona per implementarli.

Loading