PDA

View Full Version : [C++] Chiamare costruttore da un altro costruttore


vendettaaaaa
26-11-2012, 18:17
Ciao,
ho cercato un po' in rete come chiamare un costruttore da un altro, della stessa classe ovviamente. Quasi tutti dicono che non si può:
class Foo
{
public:
Foo() { /* do stuff */}
Foo(int x) : Foo() { m_x = x; } // NON COMPILA
Foo(int x)
{
Foo(); // NON COMPILA
m_x = x;
}
Foo(int x)
{
this->Foo(); // NON COMPILA
m_x = x;
}
private:
int m_x;
};
Poi è arrivato un tizio dicendo che
Foo(int x)
{
this->Foo::Foo(); // COMPILA
m_x = x;
}
compila in Visual Studio di Microsoft. Ho provato e...funziona! Così mi risparmio di trasferire il codice comune ai miei due costruttori in una funzione di servizio privata.
Ma perchè in VS funziona così e non in altri compilatori? Ok, non sarà standard C++, ma mi pare una bella "customizzazione" da avere.
E poi perchè devo specificare la classe se uso this? Cioè scrivere this->Foo::Foo() anzichè solo this->Foo() ?

L4ky
26-11-2012, 19:01
Ciao,
ho cercato un po' in rete come chiamare un costruttore da un altro, della stessa classe ovviamente. Quasi tutti dicono che non si può:
class Foo
{
public:
Foo() { /* do stuff */}
Foo(int x) : Foo() { m_x = x; } // NON COMPILA
Foo(int x)
{
Foo(); // NON COMPILA
m_x = x;
}
Foo(int x)
{
this->Foo(); // NON COMPILA
m_x = x;
}
private:
int m_x;
};
Poi è arrivato un tizio dicendo che
Foo(int x)
{
this->Foo::Foo(); // COMPILA
m_x = x;
}
compila in Visual Studio di Microsoft. Ho provato e...funziona! Così mi risparmio di trasferire il codice comune ai miei due costruttori in una funzione di servizio privata.
Ma perchè in VS funziona così e non in altri compilatori? Ok, non sarà standard C++, ma mi pare una bella "customizzazione" da avere.
E poi perchè devo specificare la classe se uso this? Cioè scrivere this->Foo::Foo() anzichè solo this->Foo() ?

Se tu hai una classe x e vuoi chiamare una qualsiasi funzione della classe y, devi per forza fare y::funzione_della_classe_y();

Ora però non so dirti perché ti si presenti quel problema..

msangi
26-11-2012, 19:10
Dai un'occhiata qui (http://stackoverflow.com/questions/308276/c-call-constructor-from-constructor)

vendettaaaaa
26-11-2012, 20:29
Dai un'occhiata qui (http://stackoverflow.com/questions/308276/c-call-constructor-from-constructor)
Ho scritto questo post dopo aver letto proprio questa discussione :D

@L4ky: ma io voglio chiamare il costruttore della classe x dalla classe x!! Cioè, quando l'utente chiama il costruttore con 4 argomenti prima viene eseguito il costruttore con 1 argomento (che fa determinate cose) ed in più opera sugli altri 3 argomenti passati. Così da evitare di riscrivere, all'inizio del costruttore con 4 argomenti, il codice che già è scritto nel costruttore con 1 argomento:
Species::Species(const char* fileName) : isTDStateDefined(false)
{
ifstream ifs(fileName);
if (!ifs) cerr << "Coudln't open file " << fileName << " - Cannot read species!" << endl;

ifs >> name >> MW;
ifs >> Tc >> Pc >> pitzer;
ifs >> cp1 >> cp2 >> cp3 >> cp4 >> cp5 >> minCpT >> maxCpT;
ifs >> hv1 >> hv2 >> hv3 >> hv4 >> minHvT >> maxHvT;
ifs >> pv1 >> pv2 >> pv3 >> pv4 >> pv5 >> minPvT >> maxPvT;

ifs.close();
}

Species::Species(const char* fileName, const double& T, const double& P, IEoS tipo)
{
this->Species::Species(fileName);

assign_TD_state(T, P, tipo);
}


Così facendo funziona, ma perchè devo specificare la classe all'interno della classe stessa?
Inoltre, lasciando perdere la formattazione poco robusta del file di input, so che sarebbe meglio delegare il compito del primo costruttore ad una funzione dedicata, tipo read_species_data(), sarebbe più giusto distinguere i compiti, ma mi sono incapponito su questo modo di operare per pura curiosità.

lorenzo001
26-11-2012, 21:33
Semplicemente così


#include <iostream>

using namespace std;

class Foo
{
public:
Foo() { cout << "Costr. 1" << endl;}

Foo(int x)
{
cout << "Costr. 2" << endl;
Foo();
m_x = x;
}

private:
int m_x;
};

int _tmain(int argc, _TCHAR* argv[])
{
Foo f(0);

return 0;
}


funziona con Visual C++ 2010 e DevC++ (mingw)

vendettaaaaa
26-11-2012, 21:51
Semplicemente così


#include <iostream>

using namespace std;

class Foo
{
public:
Foo() { cout << "Costr. 1" << endl;}

Foo(int x)
{
cout << "Costr. 2" << endl;
Foo();
m_x = x;
}

private:
int m_x;
};

int _tmain(int argc, _TCHAR* argv[])
{
Foo f(0);

return 0;
}


funziona con Visual C++ 2010 e DevC++ (mingw)
Hmm allora o ho un lapsus tremendo o qualcosa non va...potrebbe essere il lapsus :cool:
http://sdrv.ms/WT6ZLr

lorenzo001
26-11-2012, 22:08
Manca la dichiarazione della classe per controllare ... comunque ti manca il costruttore di default ... aggiungi

Species() {};

vendettaaaaa
26-11-2012, 22:35
Manca la dichiarazione della classe per controllare ... comunque ti manca il costruttore di default ... aggiungi

Species() {};
Sì mi dice che manca quello di default ma non serve, infatti se uso la riga commentata (line 34, this-> eccetera) funziona.
http://sdrv.ms/QHkLPN

lorenzo001
26-11-2012, 23:14
Non ho capito ... vedo che hai ancora i due errori ...

vendettaaaaa
26-11-2012, 23:31
Non ho capito ... vedo che hai ancora i due errori ...
E' la build di prima, stessi errori, t'ho postato l'.h della classe, come m'hai chiesto.

tomminno
27-11-2012, 07:54
Chiamare un costruttore da un altro costruttore (delegated constructors/forwarding constructors) è stato introdotto dal C++11 e questa funzionalità non è al momento supportata da Visual Studio 2012.

tomminno
27-11-2012, 08:03
Semplicemente così


#include <iostream>

using namespace std;

class Foo
{
public:
Foo() { cout << "Costr. 1" << endl;}

Foo(int x)
{
cout << "Costr. 2" << endl;
Foo();
m_x = x;
}

private:
int m_x;
};

int _tmain(int argc, _TCHAR* argv[])
{
Foo f(0);

return 0;
}


funziona con Visual C++ 2010 e DevC++ (mingw)

Ehm qui stai semplicemente creando un oggetto di tipo Foo all'interno del costruttore Foo(int x), non stai chiamando il costruttore senza parametri per inizializzare l'oggetto...

Se non sei convinto prova così:

class Foo
{
public:
Foo()
{
m_x = 4;
cout << "Costr. 1" << endl;
cout << m_x << endl;
}

Foo(int x)
{
cout << "Costr. 2" << endl;
m_x = x;
Foo();
cout << m_x << endl;
}

private:
int m_x;
};

vendettaaaaa
27-11-2012, 12:23
Ehm qui stai semplicemente creando un oggetto di tipo Foo all'interno del costruttore Foo(int x), non stai chiamando il costruttore senza parametri per inizializzare l'oggetto...

Se non sei convinto prova così:

class Foo
{
public:
Foo()
{
m_x = 4;
cout << "Costr. 1" << endl;
cout << m_x << endl;
}

Foo(int x)
{
cout << "Costr. 2" << endl;
m_x = x;
Foo();
cout << m_x << endl;
}

private:
int m_x;
};

Vero, infatti così facendo (modifica in grassetto) invece funziona come dico io:
#include <iostream>

using namespace std;

class Foo
{
public:
Foo()
{
m_x = 4;
cout << "Costr. 1" << endl;
cout << m_x << endl;
}

Foo(int x)
{
cout << "Costr. 2" << endl;
m_x = x;
this->Foo::Foo();
cout << m_x << endl;
}

private:
int m_x;
};

int main()
{
Foo a(5);

return 0;
}

marco.r
28-11-2012, 10:56
Vero, infatti così facendo (modifica in grassetto) invece funziona come dico io:

quel che stai facendo e' molto pericoloso
Considera i seguenti
- Il costruttore inizializza inizializza tutti i campi di un oggetto.
- Alcuni campi potrebbero avere tipo con costruttore non banale (ad esempio, alloco memoria)
- Non viene mai chiamato il corrispondente distruttore del primo costruttore.


Esempio

struct Foo
{
int* mem;

Foo()
{
mem = new int;
}

~Foo()
{
delete mem;
}
};



struct Bar
{
Foo foo;

Bar(){ *foo->mem = 0; }

Bar(int x)
{
this->Bar::Bar();
*foo->mem = x;
}
};

void baz()
{
Bar bar;
}


Domanda: all'uscita da baz quanta memoria ho allocato dinamicamente ? Quanta ne ho liberata ?

vendettaaaaa
28-11-2012, 11:09
quel che stai facendo e' molto pericoloso
Considera i seguenti
- Il costruttore inizializza inizializza tutti i campi di un oggetto.
- Alcuni campi potrebbero avere tipo con costruttore non banale (ad esempio, alloco memoria)
- Non viene mai chiamato il corrispondente distruttore del primo costruttore.


Esempio

struct Foo
{
int* mem;

Foo()
{
mem = new int;
}

~Foo()
{
delete mem;
}
};



struct Bar
{
Foo foo;

Bar(){ *foo->mem = 0; }

Bar(int x)
{
this->Bar::Bar();
*foo->mem = x;
}
};

void baz()
{
Bar bar;
}


Domanda: all'uscita da baz quanta memoria ho allocato dinamicamente ? Quanta ne ho liberata ?
Ma non c'è qualcosa di sbagliato? Foo foo non è un puntatore, perchè lo dereferenzi nei costruttori di Bar? Credo che la risposta dipenda da questo...se Foo foo è un oggetto, all'uscita da baz, Bar chiama il distruttore di default che a sua volta chiama il distruttore di foo perchè foo va out of scope; se foo è un puntatore ciò non avviene e rimane allocato in memoria una cella dedicata a Bar.foo->mem. Potrei sbagliarmi, ho ripreso a programmare da non molto dopo una pausa di qualche mese e i ricordi sono un po' offuscati.

marco.r
28-11-2012, 11:54
Ma non c'è qualcosa di sbagliato? Foo foo non è un puntatore, perchè lo dereferenzi nei costruttori di Bar? Credo che la risposta dipenda da questo...se Foo foo è un oggetto, all'uscita da baz, Bar chiama il distruttore di default che a sua volta chiama il distruttore di foo perchè foo va out of scope; se foo è un puntatore ciò non avviene e rimane allocato in memoria una cella dedicata a Bar.foo->mem. Potrei sbagliarmi, ho ripreso a programmare da non molto dopo una pausa di qualche mese e i ricordi sono un po' offuscati.

C'e' ovviamente una dereferenziazione di troppo
Il codice corretto e'

struct Bar
{
Foo foo;

Bar(){ *foo.mem = 0; }

Bar(int x)
{
this->Bar::Bar();
*foo.mem = x;
}
};

Il discorso e' che tu chiami sia Bar(int) che Bar().
entrambi chiamano il costruttore di Foo() che quindi alloca memoria due volte.
Ma il distruttore viene chiamato solo una volta, per cui la memoria allocata la prima volta non viene liberata.

vendettaaaaa
28-11-2012, 12:17
C'e' ovviamente una dereferenziazione di troppo
Il codice corretto e'

struct Bar
{
Foo foo;

Bar(){ *foo.mem = 0; }

Bar(int x)
{
this->Bar::Bar();
*foo.mem = x;
}
};

Il discorso e' che tu chiami sia Bar(int) che Bar().
entrambi chiamano il costruttore di Foo() che quindi alloca memoria due volte.
Ma il distruttore viene chiamato solo una volta, per cui la memoria allocata la prima volta non viene liberata.
Allora mancava anche in baz la chiamata giusta :D
Cmq ho capito, grazie per avermelo fatto notare!