Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

C++: istanziare oggetto in constructor

Raccolta di codici sorgenti

Moderatore: Foto UtentePaolino

0
voti

[1] C++: istanziare oggetto in constructor

Messaggioda Foto Utenteboiler » 17 ott 2022, 16:31

Ciao a tutti

Sto ricominciando a usare C++ dopo quasi un decennio di arrugginimento :oops:

Cerco di sintetizzare la domanda e poi vi mostro il codice:
- ho una classe B con un constructor che richiede un parametro.
- ho una classe A che ha come membro privato un'istanza di B
- come istanziare B all'interno del constructor di A se al momento non conosco ancora il parametro di inizializzazione per B?


Qui un esempio concreto (gli include guards ci sono, li ho omessi per leggibilità).
La classe B si chiama bar e implementa un counter che deve essere inizializzato. Sono disponibili i metodi per leggerne il valore e per incrementarlo di un unità:

bar.h
Codice: Seleziona tutto
class bar {

public:
   bar (unsigned int init);
   ~bar();

   unsigned int getCount();
   void incrementCount();

private:
   unsigned int _count;

};


bar.cpp
Codice: Seleziona tutto
#include "bar.h"

   bar::bar(unsigned int init)
   {
      _count = init;
   }

   bar::~bar() = default;

   unsigned int bar::getCount()
   {
      return _count;
   }

   void bar::incrementCount()
   {
      _count++;
   }


La classe A ci chiama in realtà foo ed è in sostanza un wrapper per il counter implementato in bar che incrementa il valore solo se non si è superato un certo valore massimo settato all'inizializzazione.

foo.h
Codice: Seleziona tutto
#include "bar.h"

class foo
{
public:
   foo (unsigned int max);
   ~foo();

   unsigned int getCountCapped();
   void incrementCountCapped();

private:
   const unsigned int _max;
   bar _counter;
};


foo.cpp
Codice: Seleziona tutto
#include "foo.h"

foo::foo (unsigned int max) : _max(max) {}

foo::~foo () = default;

unsigned int foo::getCountCapped()
{
   return _counter.getCount();
}

void foo::incrementCountCapped()
{
   if (_counter.getCount() < _max)
      _counter.incrementCount();
}


Questo codice non compila e credo di sapere il perché: quando chiamo il constructor per foo, il membro di tipo bar viene inizializzato anche lui, ma per farlo al compiler serve un default constructor (che non ho e che non posso avere perché non esiste un valore che per l'inizializzazione che possa essere considerato di default). Se aggiungo al codice di cui sopra un constructor che non prende argomenti e inizializza a zero, tutto funziona bene, ma non è quello che voglio.

Ho provato a risolvere transformando il membro in una referenza all'oggetto:
Codice: Seleziona tutto
private:
   bar &_counter;

In modo che il compiler debba solo riservare lo spazio per il pointer in memoria, ma nemmeno così funziona.

Come si agisce in un caso del genere?

Grazie, Boiler :ok:
Avatar utente
Foto Utenteboiler
24,6k 5 9 13
G.Master EY
G.Master EY
 
Messaggi: 5211
Iscritto il: 9 nov 2011, 12:27

0
voti

[2] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utenteboiler » 17 ott 2022, 16:44

Forse manca ancora qualche dettaglio per capire cosa voglio fare.

Come detto l'inizializzazione per il constructor di bar non la conosco ancora quando chiamo il costructor di foo, ma poco dopo sì. Il constructor di foo in realtà non si prende un integer come argomento, tre struct belli pieni.
Dopo aver fatto un paio di operazioni su di essi, posso generare il parametro di inizializzazione per bar:

Codice: Seleziona tutto
foo::foo (struct XXX, struct YYY, struct ZZZ)
{
   int initValue = XXX.a + YYY.b * ZZZ.c;
   
   // INIZIALIZZARE IL MEMBRO BAR QUI USANDO initValue
   // COME?
}


Boiler
Avatar utente
Foto Utenteboiler
24,6k 5 9 13
G.Master EY
G.Master EY
 
Messaggi: 5211
Iscritto il: 9 nov 2011, 12:27

1
voti

[3] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utentefairyvilje » 17 ott 2022, 16:49

>>>bar &_counter;
Le reference vanno inizializzate prima che il costruttore esegua il suo codice, come hai impostato _max per intenderci. Se vuoi ritardare l'inizializzazione dovresti usare un puntatore in quel caso, ed allocare in modo dinamico la memoria. Nel mentre l-uso di quell'oggetto è undefined behaviour.

Per inizializzare _counter, o lo fai nella lista argomenti fuori dal costruttore (sempre come _max; si possono usare espressioni anche complicate), o devi prevedere un costruttore che lo lascia in uno stato "sporco", ed implementare l'operatore di copia da applicare successivamente per esempio.
"640K ought to be enough for anybody" Bill Gates (?) 1981
Qualcosa non ha funzionato...

Lo sapete che l'arroganza in informatica si misura in nanodijkstra? :D
Avatar utente
Foto Utentefairyvilje
14,9k 4 9 12
G.Master EY
G.Master EY
 
Messaggi: 3037
Iscritto il: 24 gen 2012, 19:23

1
voti

[4] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utentefairyvilje » 17 ott 2022, 16:56

Un modo più avanzato per farlo è usare il displacement new. Non so se consigliartelo, è orribile da vedere, ma è tipico in ambito embedded perché evita di usare per davvero memoria dinamica, o riutilizzare in modo esplicito aree di memoria.

https://stackoverflow.com/questions/222 ... cement-new
"640K ought to be enough for anybody" Bill Gates (?) 1981
Qualcosa non ha funzionato...

Lo sapete che l'arroganza in informatica si misura in nanodijkstra? :D
Avatar utente
Foto Utentefairyvilje
14,9k 4 9 12
G.Master EY
G.Master EY
 
Messaggi: 3037
Iscritto il: 24 gen 2012, 19:23

0
voti

[5] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utenteboiler » 17 ott 2022, 17:33

Ti ringrazio per l'intervento.

Se capisco bene, però, questo mi obbligherebbe al generare un pointer del tipo bar nell'entità sopra a foo, e quindi a propagare inutilmente dettagli di implementazione verso l'alto. E da quello che mi sembra di vedere non c'è modo di evitarlo, a meno di creare il default constructor per bar (che manderebbe all'aria tutto il concetto che ci sta sotto: il constructor riceve tramite struct la configurazione e fa un sanity check, la configurazione è const e quindi da qual momento immutabile!).
Anche con il placement new, se non sbaglio, devo conoscere le dimensioni dell'oggetto che ci andrà per invocare malloc nell'entità che sta sopra a foo. E a questo punto o uso sizeof, lasciando trapelare nuovamente tutta la classe dove in realtà non serve, o ne tengo le dimensioni in un #define (mi vengono i brividi solo a pensarci!).

Se quello che ho scritto sopra è giusto, allora...
è orribile da vedere

... questa affermazione è applicabile a tutto il C++ cosiddetto moderno.
Su molti frangenti ho avuto l'impressione di aver a che fare con una lingua medioevale nella quale sono stati introdotti a forza (=stupro) dei costrutti sintattici orribili per permetter l'uso di paradigmi di programmazione moderni.

Ne parlerò con l'architetto che ci segue quando torna dalle vacanze. Nella mia ignoranza vedo tre opzioni:
- mandare a ***** quanto fatto finora e rendere la configurazione delle classi mutabile
- mandare a ***** quanto fatto finora e rendere la classe bar accessibile all'entità che istanzia foo
- usare un hack esotico con il placement new

Ancora grazie, non hai fatto aumentare la mia simpatia nei confronti del C++, ma credo di aver imparato qualcosa :mrgreen:
Boiler
Avatar utente
Foto Utenteboiler
24,6k 5 9 13
G.Master EY
G.Master EY
 
Messaggi: 5211
Iscritto il: 9 nov 2011, 12:27

0
voti

[6] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utenteboiler » 18 ott 2022, 9:09

Ho semi-spostato & risolto (forse, molto forse!) il problema.

Ho aggiunto alla classe bar un metodo statico che crea la configurazione da passare al constructor.
Ho anche cambiato il tipo di _bar da bar& a std::unique_ptr<bar>
La chiamata del constructor di foo ora ha questo aspetto.
Codice: Seleziona tutto
foo::foo(uint8_t x, uint8_t y) :
  _bar(new bar(x, bar::ConfigBuilder(1, 1, y, false))) {}


Compila, ma non so ancora se funziona (non ho qui con me l'hardware su cui deve "girare").

Boiler
Avatar utente
Foto Utenteboiler
24,6k 5 9 13
G.Master EY
G.Master EY
 
Messaggi: 5211
Iscritto il: 9 nov 2011, 12:27

0
voti

[7] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utentefairyvilje » 19 ott 2022, 11:18

boiler ha scritto:E a questo punto o uso sizeof, lasciando trapelare nuovamente tutta la classe dove in realtà non serve

No perché? Questo modo è ideomatico in C++. La dimensione di una classe è nota in tempo di compilazione, non crea nessuna reale dipendenza.

boiler ha scritto:... questa affermazione è applicabile a tutto il C++ cosiddetto moderno.
Su molti frangenti ho avuto l'impressione di aver a che fare con una lingua medioevale nella quale sono stati introdotti a forza (=stupro) dei costrutti sintattici orribili per permetter l'uso di paradigmi di programmazione moderni.

Fondamentalmente è così. OOP e 0-overhead per astrazione sono concetti difficili da coniugare, e portano ad una lunga serie di idiosincrasie. Fondamentalmente la maggior parte del suo design è un grosso compromesso.

Ad onor del vero ci sono probabilmente metodi più eleganti per risolvere il problema da te posto, solo che per come è stato descritto mancano i dettagli contestuali che consentirebbero di ricostruire le classi in una forma meno convoluta.
"640K ought to be enough for anybody" Bill Gates (?) 1981
Qualcosa non ha funzionato...

Lo sapete che l'arroganza in informatica si misura in nanodijkstra? :D
Avatar utente
Foto Utentefairyvilje
14,9k 4 9 12
G.Master EY
G.Master EY
 
Messaggi: 3037
Iscritto il: 24 gen 2012, 19:23

0
voti

[8] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utentegvee » 19 ott 2022, 12:13

Scusate l'intromissione, solo per curiosità:
boiler ha scritto:(...) non esiste un valore che per l'inizializzazione che possa essere considerato di default (...)

Perché ?
Avatar utente
Foto Utentegvee
1.180 2 7
Stabilizzato
Stabilizzato
 
Messaggi: 414
Iscritto il: 11 feb 2018, 20:34

0
voti

[9] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utenteboiler » 20 ott 2022, 9:18

fairyvilje ha scritto:
boiler ha scritto:E a questo punto o uso sizeof, lasciando trapelare nuovamente tutta la classe dove in realtà non serve

No perché? Questo modo è ideomatico in C++. La dimensione di una classe è nota in tempo di compilazione, non crea nessuna reale dipendenza.

Per poter usare sizeof devo però includere bar.h nell'entità che istanzia foo. Insomma devo fornire informazioni sulla classe anche a chi in realtà non dovrebbe usarla (e nemmeno sapere che esiste).

Insomma, sto cercando di ottenere l'architettura a sinistra (che mi sembra pulita) e mi vedo quasi-costretto a realizzare quella a destra (che nel contesto delle classi in C++ mi sembra equivalete al "mettere tutto in variabili globali" in C).



Nel frattempo ho modificato la classe: il membro bar non è piú una referenza ad un oggetto, ma l'oggetto stesso. Al momento compila e funziona. Poi ne parlerò con l'architetto.

fairyvilje ha scritto:Fondamentalmente è così. OOP e 0-overhead per astrazione sono concetti difficili da coniugare, e portano ad una lunga serie di idiosincrasie. Fondamentalmente la maggior parte del suo design è un grosso compromesso.

Sì, dopo aver scritto la risposta alla quale ti riferisci mi sono calmato e mi sono chiesto se fosse uno sfogo mio dovuto alla frustrazione o se ci fosse del vero e ho ricercato un po'. Ci sono effettivamente voci autorevoli (Linus Torvalds e Donald Knuth) che confermano quanto scritto. Un po' mi ha fatto piacere :mrgreen:
Vabbè... è lo standard in campo embedded, ci dobbiamo convivere.

fairyvilje ha scritto:mancano i dettagli contestuali che consentirebbero di ricostruire le classi in una forma meno convoluta.

Ne sono conscio, ma essendo roba di lavoro sono costretto ad astrarre e fornire esempi sintetici. Cerco di metterci tutto quello che considero importante, ma ovviamente non è the real thing.

Saluti, Boiler
Avatar utente
Foto Utenteboiler
24,6k 5 9 13
G.Master EY
G.Master EY
 
Messaggi: 5211
Iscritto il: 9 nov 2011, 12:27

0
voti

[10] Re: C++: istanziare oggetto in constructor

Messaggioda Foto Utenteboiler » 20 ott 2022, 9:28

gvee ha scritto:Scusate l'intromissione, solo per curiosità:

È un forum. È fatto per le intromissioni :ok:

boiler ha scritto:(...) non esiste un valore che per l'inizializzazione che possa essere considerato di default (...)

Perché ?

Per il come abbiamo realizzato la struttura della classe.
Quando chiami il constructor, gli passi la configurazione. Questa viene memorizzata in membri const, quindi immutabili. Il constructor fa poi anche un sanity check su questi parametri e se non sono accettati setta un flag che indica l'errore.

Dopo aver istanziato la classe, chi vuole usarla deve chiamare il metodo init, che prende la configurazione e la applica a quello che la classe effettivamente fa. Se il flag che indica l'errore è attivo, init non attiva la classe.

L'idea che ci sta dietro, a grandi linee, è di evitare i problemi risultati dalla modifica della configurazione con la classe già istanziata e attiva.

Non posso quindi averla in uno stato di default che dovrò poi modificare.

Inoltre ci sono altri motivi (indipendenti dalla configurazione) per i quali init potrebbe rifiutarsi di attivare la classe. Se l'errore si verifica sempre e solo in init, per l'entità che ci sta sopra gestire gli errori è piú facile.

Come ha scritto sopra fairyvilje, probabilmente si può risolvere anche diversamente. Per il momento questa strada sembra funzionare, anche se è la causa di questo problema che mi ha portato ad aprire il thread. Ma si risolverà ;-)

Boiler
Avatar utente
Foto Utenteboiler
24,6k 5 9 13
G.Master EY
G.Master EY
 
Messaggi: 5211
Iscritto il: 9 nov 2011, 12:27

Prossimo

Torna a Firmware e programmazione

Chi c’è in linea

Visitano il forum: Nessuno e 5 ospiti