Newsgroup: it.comp.lang.c++
Titolo: Problemi di design con classi globali

In un programma (il solito MUD che sto tentando di scrivere :-) ) ho una
serie di dati che devono avere visibilità globale, e una serie di funzioni
che operano su di essi. Una soluzione per realizzare ciò sarebbero variabili
e funzioni globali, ma la cosa in C++ mi ripugna un po'. Ho quindi pensato,
siccome questi dati e funzioni sono logicamente raggruppabili, di riunirli
in classi apposite. Il problema però è questo: mettiamo di avere tre classi,
chiamate ad esempio network, players e maindb, contenenti rispettivamente le
funzioni per gestire le connessioni di rete, i personaggi in gioco e il
database di stanze e oggetti del mondo. Se le definisco come semplici
oggetti, devo istanziarli globalmente, ad esempio così:
class network
{
    // ...
};
class players
{
    // ...
};
class maindb
{
    // ...
};
network nw;
players pl;
maindb db;
Siccome i costruttori di queste classi devono effettuare le varie
inizializzazioni del programma, se le dichiaro così ho qualche garanzia
sull'ordine di chiamata dei costruttori, oppure gli oggetti globali vengono
creati in un ordine casuale ?
Una soluzione sarebbe dichiarare nulli i costruttori e dare ad ogni classe
un metodo init(), ma mi sembra molto poco elegante. Un'altra sarebbe
dichiarare dei puntatori globali a queste classi, e istanziarli poi con una
chiamata a new, in modo da poter gestire l'ordine di creazione degli
oggetti. Ma ogni volta che occorresse un riferimento a questi oggetti,
bisognerebbe prima dereferenziare i puntatori (*)... un'altra soluzione,
visto che di questi oggetti ne deve comunqe esistere uno solo per tipo,
sarebbe quella di dichiarare tutti i membri static, e usarli senza
istanziare alcunché. Ma poi non avrei nessuna forma di costruttori e dovrei
utilizzare inizializzazioni esplicite, oltre a dover richiamare ogni volta i
metodi qualificando completamente il nome della classe, ad esempio con
network::update() e cose simili...
Quale può essere un buon approccio a questo problema ?
Massimo

> Quale puņ essere un buon approccio a questo problema ?
L'incapsulamento.
Io sono un maniaco della programmazione ad oggetti e nel tuo caso
implementerei una classe MUD che contiene tutta l'implementazione del tuo
programma e di conseguenza i dati globali risulteranno essere membri della
classe principale.

 
A prima vista direi che potresti far uso di singleton, con l'ausilio
di un unico oggetto globale che controlli l'ordine di instanziazione e di
distruzione.
Max

 globali vengono creati in un ordine casuale ?
In una singola translation unit l'ordine è garantito.
Se però usi diverse translation unit la cosa si complica.
Una soluzione potrebbe essere quella adottata per inizializzare
std::cout, std::cin, std::cerr e std::clog.
Manlio Perillo

 > Quale può essere un buon approccio a questo problema ?

Ok, ma a quel punto tanto vale usare variabili/funzioni globali... io volevo
anche raggruppare con un minimo di logica dati e metodi legati fra loro!
Massimo

 distruzione.
Ehm... me la riformuli in italiano ? ;-)
Massimo

 std::cout, std::cin, std::cerr e std::clog.
...e cioè ?
Massimo


Ecco una soluzione molto completa (almeno spero/credo, mi è venuta
in mente ora).
Per sempicità e per permettere un poco di debug uso una variabile
globale di tipo std::vector<int>:
///////////////////////////////////////////////////////
<init.hpp>
class Init
{
   private:
      static int count;
   public:
      Init();
      ~Init();
};
///////////////////////////////////////////////////////
<init.cpp>
#include "init.hpp"
#include "global.hpp"    // (non c'è bisogno)
#include <iostream>      // per debug
int Init::count = 0;
// variabile ausiliaria
std::vector<int>* global_vector_aux;
Init::Init()
{
   if(count++ == 0)
   {
      // inizializza ...
      std::cout << "inizializzazione...\n";
      global_vector_aux = new std::vector<int>;
   }
}
Init::~Init()
{
   if(--count == 0)
   {
      // distrugge ...
      std::cout << "distruzione...\n";
      delete global_vector_aux;
   }
}
// *definizione* della variabile globale:
//
// E' corretto fare così??
//
std::vector<int>& global_vector = *global_vector_aux;
//
// Usando una referenza si assicura che il life-time di
// global_vector sia quello stabilito da Init.
//
// In questo modo si ha il *pieno* controllo dell'ordine
// di costruzione e distruzione
// (utile nel caso di più variabili globali che si riferiscono
//  l'un l'altra).
//
///////////////////////////////////////////////////////
<global.hpp>
//
// Header contenente le *dichiarazioni* degli oggetti globali
//
#include <vector>
//
// Nota: è una referenza.
//
extern std::vector<int>& global_vector;
///////////////////////////////////////////////////////
<common.hpp>
//
// header da includere in ogni translation unit
//
#include "init.hpp"
namespace impl
{
   Init my_init;
}
// Nota: il KAI C++ (per iostream> fa static Init my_init;
// ma credo che lo static sia inutile.
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
Ed ora il file main:
<main.cpp>
#include "common.hpp"
#include "global.hpp"
#include <iostream>
int main()
{
   global_vector.push_back(8);
   std::cout << global_vector[0] << '\n';
}
///////////////////////////////////////////////////////
Quindi per compilare il programmino basta fare:
bcc32 main.cpp init.cpp
Nota:
Come si vede la cosa è abbastanza elaborata,
ma ha il pregio di essere generale.
Mi farebbe piacere sapere se la cosa è corretta
(ho qualche piccolo dubbio, credo infondato).
Mi farebbe anche piacere sapere se questa tecnica
(usare una referenza per la variabile globale)
è già nota o me la sono 'inventata' io.
Manlio Perillo

 std::vector<int>* global_vector_aux;
Ovviamente global_vector_aux andrebbe definita in:
namespace
{
    std::vector<int>* global_vector_aux;
}
In questo modo non è accessibile da altre translation units.
Un'ultima considerazione.
Se si hanno più gruppi di variabili globali da inizializzare
si possono usare diverse classi Init.
Ad esempio:
namespace A
{
    class Init { // ... };
}
namespace B
{
    class Init { // ... };
}
.....
namespace A
{
    // variabili globali raggruppate logicamente fra loro
}
namespace B
{
    // altre variabili globali raggruppate logicamente fra loro
}
(Comunque è meglio non esagerare con le variabili globali, ed in
 ogni caso sfruttare sempre i namespace).
P.S.
Anche i namespace mi sembrano un poco 'trascurati' in questo
newsgroup.
Manlio Perillo


C'č anche un'altro modo (che mi č venuto in mente adesso, ed č
probabilmente quello usato da iostream).
Innanzi tutto la variabile globale non necessita piů di essere una
referenza.
Quindi basta fare:
// *definizione* della variabile globale
typedef std::vector<int> vector;
vector global_vector;
Init::Init()
{
   if(count++ == 0)
   {
      // inizializza ...
      global_vector.~vector();  // <- necessario
      new(&global_vector) vector;
   }
}
Init::~Init()
{
   if(--count == 0)
   {
      // distrugge ...
      global_vector.~vector();
   }
}
Tuttavia credo che questa soluzione sia meno flessibile della
precedente.
P.S.
Scusate se insisto, ma credo che questa sia una problematica di
interesse generale;
interessa anche a me sapere se questa tecnica č valida.
P.S. 2
Se faccio:
    std::vector<int> v;
    v.~vector<int>();
il BCC 5.5 non me lo compila, dicendomi:
Error: Destructor name must match the class name in function
       Init::Init()
mentre il DJGPP non fa storie.
Come mai?
Manlio Perillo

 anche raggruppare con un minimo di logica dati e metodi legati fra loro!
Non sono proprio daccordo. Nel caso di variabili globali ti tocca
dichiararle con delle extern in ogni file di codice o nei rispettivi include
e per le eventuali funzioni globali che dovresti definirti ... tanto vale
che usi il C e non il C++ allora!
Inoltre la programmazione ad oggetti è proprio l'ideale per legare dati e
metodi correlati logicamente tra di loro. Se non vuoi fare programmazione ad
oggetti ovviamente è un altro discorso.

 Mi piacerebbe sapere perchè fa così.
Aargh, e' deprecato! Anche se l'effetto e' lo stesso.
Ciao,
Nicola

 [...]
Chiedo scusa.
Probabilmente mi sono confuso con l'altro esempio.
E' un errore (grave) chiamare il distruttore in questo
caso.
Manlio Perillo

Aargh, e' deprecato! Anche se l'effetto e' lo stesso.
Credo che sia *quasi* lo stesso. Uno dei problemi che avevo
visto nel C++ quando ho iniziato a guardarlo per la prima
volta (qualche anno fa) era che se in un modulo mi serviva
una classe "locale" per soli scopi di implementazione non
avevo la possibilita' di dichiararla senza rischiare
sovrapposizioni di nome che potevano portare a clash.
Un unnamed namespace risolve questo problema... in pratica
si possono definire classi "di comodo" senza creare
potenziali conflitti al momento del link.
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 potenziali conflitti al momento del link.
Probabilmente mi sfugge qualcosa. Se ricordo bene, le dichiarazioni
hanno al massimo visibilità di modulo e non hanno linkage. Quindi non
riesco a capire da dove possono derivare i problemi di name clash.
Delucidami...
EMG

Delucidami...
Considera un programma fatto di tre moduli... main.cpp, obj1.cpp
e obj2.cpp...
//// File obj1.h ////
class obj1
{
  public:
    void doit();
};
//// File obj1.cpp ////
#include <iostream>
#include "obj1.h"
void obj1::doit()
{
  std::cout << "obj1::doit" << std::endl;
}
//// File obj2.h ////
class obj2
{
  public:
    void doit();
};
//// File obj2.cpp ////
#include <iostream>
#include "obj2.h"
// la classe obj1 in questo modulo e' una classe
// "di comodo"; chi ha scritto questo sorgente
// non conosceva affatto l'esistenza del modulo
// obj1.cpp
class obj1
{
  public:
    void doit();
};
void obj2::doit()
{
  obj1 o1;
  o1.doit(); // <<--- Attenzione
}
void obj1::doit()
{
  std::cout << "obj1::doit() (from obj2::doit())" << std::endl;
}
//// File main.cpp ////
#include "obj1.h"
#include "obj2.h"
int main()
{
  obj1 o1;
  obj2 o2;
  o1.doit();
  o2.doit();
}
In pratica il modulo obj1.cpp dichiara una classe "obj1", il
modulo obj2.cpp dichiara una classe con lo stesso nome "obj1"
diversa (nota che obj1.cpp e obj2.cpp non hanno nessuna
relazione fra loro... potrebbero essere parti lontanissime
di uno stesso progetto).
Quando il compilatore incontra l'istruzione che ho marcato
con "<<--- Attenzione" segna che deve chiamare la funzione
"void obj1::doit()" (che non sa se sara' dichiarata nello
stesso modulo) e quindi genera un riferimento che dovra'
essere risolto al momento del linking.
Il problema e' che al momento del link esisteranno due
funzioni diverse (e senza nessuna relazione fra loro) che
corrispondono esattamente a "void obj1::doit()".
Eseguendo il programma completo una volta compilato con BCC55
con il comando "bcc32 main.cpp obj1.cpp obj2.cpp" ottieni
il seguente output:
     obj1::doit
     obj1::doit
Nota che la seconda linea e' completamente errata... se
metti una stampa nel costuttore di default inline della
classe obj1 definita in obj2.cpp la stampa viene eseguita!
In pratica viene chiamato un metodo di obj1 definito da
obj1.cpp per una istanza di obj1 definito da obj2.cpp.
Il risultato netto e' che il modulo obj2 smette di
funzionare correttamente semplicemente perche' nel progetto
e' presente un modulo che definisce una classe che si
chiama obj1.
Nota anche che se invece compili il programma con
"bcc32 main.cpp obj2.cpp obj1.cpp" allora l'output diventa
    obj1::doit() (from obj2::doit())
    obj1::doit() (from obj2::doit())
Che e' ovviamente altrettanto errato (in questo caso viene
chiamato il metodo definito in obj2.cpp anche per l'istanza
di obj1 creata dal main che avrebbe voluto essere una istanza
dell'oggetto dichiarato in obj1.h).
Nota anche che ad esempio il gcc genera un errore fatale
nella fase di link con il messaggio "multiple definition
of `obj1::doit(void)'".
Quello di cui sentivo in quel momento la carenza nel C++
(parlo di qualche anno fa) era la possibilita' di scrivere
    static class obj1
    {
      ...
    };
per limitare la visibilita' di classi di comodo.
Questo problema e' stato risolto dall'introduzione del
namespace senza nome.
Che la cosa venga segnalata o meno resta il problema
che senza namespace un clash di nome classe "locale" puo'
rendere un programma inutilizzabile (o incompilabile).
HTH


 e obj2.cpp...
Quindi le dichiarazioni di classe hanno visibilità globale come le
funzioni? Se è così allora ti capisco quando dici che la keyword static
avrebbe fatto comodo anche per quelle. E che mi dici delle dichiarazioni
di classe all'interno di un blocco?
EMG

 e obj2.cpp...
Scusa ma il problema e' diverso. In un grande progetto c'e' sempre la 
possibilita' di conflitto di nomi, e deve essere risolto a monte. 
Non mi piace in questo senso l'implementazione dei namespace. In effetti il 
problema sussiste lo stesso se i due moduli anno lo stesso namespace ( 
namespace obj/ class obj : e' troppo banale perche' qualcuno NON ci caschi 
). E il tuo sistema del namespace "senza nome" non mi sembra usabile se la 
tua classe di appoggio e' distribuita su piu' file.
 Se invece e' in un unico oggetto basta dichiarare i metodi static... no 
azz ! non funziona piu' !! che storia e' questa !! 
 Mi sembrerebbe molto piu' intelligente il sistema utilizzato dalle DLL 
sotto windows : posso creare delle librerie partendo da piu' moduli oggetti 
e cecidere ( da queste librerie ) cosa esportare. In questa maniera si 
potrebbe generare una gerarchia di moduli oggetto, al contrario del sistema 
attuale in cui un modulo oggetto e' importante quanto tutti gli altri...
Pero' bisognerebbe poter accorpare le varie librerie potendo ridecidere 
cosa esportare
Per fare questo bisognerebbe riprogettare i linker ( ma non mi sembra poi 
una cosa impossibile !! )

 una cosa impossibile !! )
Benche' l'esempio delle DLL mi pare poco calzante (e comunque nessuno ti
vieta di utilizzarle sotto Windows) in realta' non hai tutti i torti.
Pero' la soluzione che a me pare migliore sarebbe l'adozione di un
costrutto di piu' alto livello, ovvero quello che in ADA si chiama
package ed in Delphi Unit.
Cioe' un costrutto volto all'organizzazione del codice, tradotto dal
compilatore in un singolo oggetto e che esporta esplicitamente ed in
modo verificato una parte dei costrutti definiti al suo interno.
Ciao,
Nicola


Scusa, ma che cosa centrano ora i metodi static?
Manlio Perillo


La classe obj1 nell'esempio di Andrea ha visibilità globale perchè
*è* globale.
Se una classe è dichiarata in un blocco è visibile solo li.
Manlio Perillo

 Se una classe è dichiarata in un blocco è visibile solo li.
E su questo siamo d'accordo.
EMG

 header file).
Per le funzioni, ad esempio, non è così.
In  C non è necessario includere la definizione di una
funzione per poterla usare.
Il problema con questo è che il compilatore non ha informazioni
sufficienti per controllare che stiamo passando gli argomenti
giusti.
Inoltre non si applicano nemmeno le conversioni sui parametri
passati.
Ecco un esempio:
void f(char* p);
int main()
{
    f(0);
}
Se non ci fosse la dichiarazione di f potremmo avere dei problemi,
in quanto char*(0) potrebbe avere una rappresentazione diversa
da 0 (intero).
Con la definizione della funzione, invece, 0 viene convertito in
char*(0) senza problemi.
A quanto mi risulta solo le costanti (a meno che non dichiarate
extern) e i typedefs hanno linkage interno.
Manlio Perillo

 extern) e i typedefs hanno linkage interno.
Infatti le dichiarazioni non hanno linkage. Per questo non riesco a
capire come possano quelle di classe avere visibilità globale. Qualcosa
continua a sfuggirmi...
EMG


Allora è questo che non ti è chiaro?
Il fatto è che la dichiarazione di una classe è anche una
definizione della stessa.
Da Stroustrup 10.2:
"The construct
   class X { ... };
 is called a class definition because it defines a new type.
 For historical reasons, a class definition is often referred
 to as a class declaration. Also, like declarations that are
 not definitions, a class definition can be replicated in
 different source files using #include without violating the
 one-definition rule".
Era questo il problema o ti ho frainteso di nuovo?
Manlio Perillo

 Era questo il problema o ti ho frainteso di nuovo?
Si, era questo. Ma allora mi pare di capire che non ci può essere
conflitto di nome tra tipi definiti in moduli diversi, giusto? Invece
Andrea Griffini asserisce il contrario. Chi ha ragione?
EMG
PS. A meno che non abbia capito niente io di quello che voleva dire
Andrea, in questo caso prego il medesimo di spiegare.


Non sono sicuro di aver capito bene quello che stai dicendo.
Nell'esempio di Andrea la ODR non era rispettata.
Il gcc se ne accorto mentre il BCC no.
Ti riporto cosa scrive lo Stroustrup riguardo la ODR[9.2.3]:
"...two definitions of a class, template, or inline function are
 accepted as examples of the same unique definition if and only if
    [1] they appear in different translations unit, and
    [2] they are token-for-token identical, and
    [3] the meanings of those tokens are the same in both
        translation units.
 ...
 Checking against inconsistent class definitions in separate
 translation units is beyond the ability of most C++
 implementations. Consequently, declarations that violate the
 ODR can be a sorce of subtle errors."
Un'ultima cosa.
Quando parli di moduli diversi cosa intendi?
Translations unit oppure namespace?
Manlio Perillo

 spiegare.
La cosa e' partita quando si e' parlato degli unnamed
namespace e di come questo fosse la stessa cosa di usare
la parola static per le globali.
Io ho semplicemente detto che non e' la stessa cosa quando
si devono definire delle classi visto che static con le
classi non si puo' usare.
Molto tempo fa, quando ho iniziato a guardare per la prima
volta il C++, i namespace non esistevano ancora e una carenza
che mi era parsa evidente era l'impossibilita' di definire
delle classi "di comodo" senza rischiare di creare problemi
al progetto. Io in pratica sentivo la mancanza di
   static class Blabla
   {
     ...
   };
che potesse mantenere il nome Blabla e i suoi derivati
confinati a livello di modulo (un po' come una variabile
int dichiarata static). Prova ad immaginare cosa poteva
significare scrivere una libreria in C++ ... certamente
non si sarebbe potuto chiamare una classe, ad esempio,
"Polygon" ... immagini quanti utlizzatori della libreria
avrebbero potuto scegliere un nome simile loro uso ?
I namespace hanno affrontato il problema in maniera piu'
generale e hanno anche fornito la possibilita' di fare
quello che volevo usando un namespace senza nome.
Ora posso scrivere:
   namespace {
     class Blabla
     {
       ...
     };
   }
senza dovermi preoccupare di potenziali conflitti.
I problemi di sovrapposizione di nomi sono dovuti al
fatto che quando scrivi
  class myclass
  {
    public:
      myclass();
  };
hai detto che esistera' un costruttore pubblico per
un oggetto di classe myclass chiamato myclass::myclass()
(e un distruttore, un operatore di assegnazione,
un costruttore di copia e un paio di altre cosette)
hai fatto cioe' una specie di prototype.
Come capirai se qualche altra parte del progetto dovesse
definire una classe chiamata myclass ci sarebbe per il
linker un po' di confusione...
Infatti in C++ non e' permesso che una stessa classe
abbia due definizioni distinte; ma non sono sicuro che
un compilatore conforme deve necessariamente segnalare
il problema (come fa gcc): se non sbaglio sugli errori che
richiedono una analisi di piu' compilation unit per essere
scoperti lo standard e' in generale permissivo.
Spero di non aver aumentato i dubbi :)
Andrea
--
----------------------------------------------------------------
 <\___/>  Andrea        email: agriff at tin dot it
 / . . \  "6502"          irc: _6502_@EFNET#italia
(  (")  ) Griffini   homepage: 
http://www.netservers.com/~agriff
Sent via Deja.com 
http://www.deja.com/
Before you buy.

 Translations unit oppure namespace?
Translation unit.
EMG

 Spero di non aver aumentato i dubbi :)
Non direi. Ma non li ho ancora risolti.
EMG

sono invece locali non vedo proprio da dove possa sorgere il conflitto.
Spessissimo i metodi sono normali funzioni C che ricevono
semplicemente un parametro all'inizio passato implicitamente
dal compilatore (il "this").
Nel caso di binding statico una chiamata del tipo
    obj.method(x);
e' internamente implementata come
    __obj_method_vi(&obj,x);
(dove con __obj_method_vi ho inventato una convenzione
di costruzione del nome globale: vi = void con un
parametro intero).
Come comprenderai non sarebbe possibile la coesistenza
di due simboli globali con lo stesso nome
(es. __obj__method_vi) nello stesso progetto.
Il problema esiste anche per la VMT ... che nome
dovrebbe avere nel progetto globale se dovessero
esistere classi diverse con lo stesso nome ?
Questo assolutamente non ha nulla a che vedere con il
fatto che le istanze siano locali o globali ... ma
a come risolvere le chiamate a costuttori e metodi.
Forse la cosa si potrebbe risolvere utilizzando
nella costruzione dei nomi globali un checksum di
tutta la definizione della classe (lasciando pero' la
liberta' permessa dalla ODR)... ma lo standard non
prevede che un compilatore debba cercarsi queste
grane per permettere la coesistenza di classi diverse
con lo stesso nome.
Se fra l'altro guardi il codice di esempio che di cui
ho fatto il posting i due oggetti erano creati come
istanze locali di main e il conflitto era comunque
presente.


 errato, anche se il compilatore/linker potrebbe non avvisare?
Gia'. Sostanzialmente lo standard dice: noi ti avevamo avvisato...
 
Ciao,
Nicola

 esistere classi diverse con lo stesso nome ?
Tra l'altro, proprio per questo conviene stare attenti a non usare
troppi nomi troppo lunghi, ad esempio, se definisco:
namespace LaMiaLibreria {
class LaMiaClasseArgomento {};
template<typename T> class LaMiaClasseTemplate {
  public:
    static LaMiaClasseTemplate * LaMiaFabbrica() { 
      return new LaMiaClasseTemplate; }
};
Il compilatore tradurra' una specializzazione del mio metodo statico in
qualcosa come:
nsLaMiaLibreria_cltLaMiaClasseTemplate_taLaMiaClasseArgomento_rtLaMiaClasseTemplate_vd
ed il linker dira' che non supporta nomi cosi lunghi (o che due nomi
portano allo stesso mangling). Conme dicono gli americani, been there,
done that.
Ciao,
Nicola


Si, č proprio cosě.
Manlio Perillo

 la situazione si complica non poco....
Qui forse č meglio chiarire un poco.
Innanzitutto vale la pena ricordare che
    namespace { ... }
č equivalente a
    namespace nome_univoco { ... }
    using namespace nome_univoco;
Ecco ora un esempio probabilmente ovvio:
********************** <uno.cpp> ******************
namespace
{
   struct foo
   {
      void f();
   };
}
void f()
{
   foo obj;
   obj.f();
}
*********************** <due.cpp> *******************
// tentativo di definire foo::f() di uno.cpp
struct foo
{
  void f();
};
void foo::f() { }
Compilando questo programma si ottiene giustamente un errore
in fase di linking.
Il BCC 5.5, ad esempio, dice:
Error: Unresolved external '__UNNS__uno_01c04f1269fbf720::foo::f()'
       referenced from ...\UNO.OBJ
Questo significa (credo) che la classe foo definita in un namespace
anonimo ha linkage interno.
Quindi ha senso parlare di linkage per una classe.
Riporto anche un'altro esempio:
*********************** <main.cpp> ********************
struct foo
{
   void f();
};
int main()
{
   foo obj;
   obj.f();
}
******************** <uno.cpp> ************************
namespace
{
   struct foo
   {
      void f();
   };
}
void foo::f() { }
Anche qui si ottiene un errore in fase di linking:
Error: Unresolved external 'foo::f()' referenced from
       ...MAIN.OBJ.
Questo comportamento č molto importante.
Manlio Perillo

 (es. __obj__method_vi) nello stesso progetto.
Bravissimo! Grazie. Hai chiarito tutti i miei dubbi. Io cercavo una
spiegazione al livello del C++ mentre invece andava cercata ad un
livello leggermente più basso.
EMG

 dichiarate inline)
Bene, ormai ho capito perfettamente, grazie a tutti.
EMG

 Quindi ha senso parlare di linkage per una classe.
O dei suoi metodi? Continuo ad avere la sensazione che certe definizioni
siano state inventate a posteriori, e che contrastino con altre scelte,
tipo quella di supportare un modello di linking decisamente primitivo.
Ciao,
Nicola


Anche se hai ragione non credo sia una cosa così problematica
(o no?).
Dire che una classe ha linkage interno, significa che i suoi
metodi hanno linkage interno.
Certamente la cosa può portare ad un poco di confusione.
(La dichiarazione/definizione di una classe non è come la
 dichiarazione di una funzione, ma serve a generare istanze
 di quella classe).
Manlio Perillo

  di quella classe).
In effetti ha due funzioni: serve a dichiarare una struttura (ovvero una
lista di offset) e, sostanzialmente come un namespace, a isolare i nomi
delle funzioni definite come membri. Tutto il resto se lo mangia il
compilatore
Ciao,
Nicola


Questo probabilmente(?) è dovuto al fatto che il linker
deve restare compatibile con il C.
La compatibilità con il C è, in effetti, un'arma a "doppio taglio".
Manlio Perillo