Newsgroup: it.comp.lang.c++
Titolo: Baco in MS VisualC++? (lo so, lo so...)

Lo so  che non ci  si azzecca quasi  mai quando si pensa  ad un baco  in un
compilatore, ma  sospetto un baco  im Microsoft Visual C++.   Ho verificato
questo comportamento  su 4.0  e amici mi  dicono che  fa lo stesso  su 5.0.
Sotto egcs compila regolarmente, mentre sotto VC++ da` errore:
muro.cpp(164) : error C2248:
 'mattone' : cannot access private struct declared in class 'Muro::Colonna'
Allego il file incriminato (e`  la soluzione di un esercizio d'esame).  Per
farlo compilare da VC++ basta  dichiarare la struct mattone senza definirla
e definirla 6 righe piu` giu`, nella parte pubblica della classe Colonna.
Commenti graditi, grazie.
===============================================================
#include <iostream.h>		// class iostream
#include <stdlib.h>		// exit()
#include <new.h>		// set_new_handler()
void errore (char *s) { cerr << s << endl; exit (1); }
void mem_err () { errore("Memoria esaurita"); }
int
main ()
{
  set_new_handler (mem_err);
  /* ... */
  return 0;
}
enum colore { BIANCO, ROSSO, GIALLO, VERDE };
class Muro
{
  class Colonna
  {
    struct mattone { colore c; mattone *next; };
    mattone *primo;
    mattone *ultimo;
    int nmattoni;
    void distruggi ();
    Colonna (const Colonna& col);       // disabilita il costruttore di copia
  public:
    Colonna () { nmattoni = 0; }
    ~Colonna () { distruggi(); }
    Colonna& operator= (const Colonna&);
    int altezza () const { return nmattoni; }
    void allunga (colore);
    void accorcia ();
    friend ostream& operator<< (ostream&, const Muro&);
  };
  Colonna *muro;
  int ncolonne;
  void copia (const Muro&);
  Colonna& alta () const;
  Colonna& bassa () const;
public:
  Muro (int n = 1);
  Muro (const Muro& m) { copia(m); }
  ~Muro () { delete [] muro; }
  Muro& Muro::operator= (const Muro&);
  void aggiungi (const colore c) { bassa().allunga(c); }
  void togli () { alta().accorcia(); }
  friend ostream& operator<< (ostream&, const Muro&);
};
void
Muro::Colonna::distruggi ()
{
  while (nmattoni-- > 0)
    {
      mattone *m = primo;
      primo = primo->next;
      delete m;
    }
}
Muro::Colonna& 
Muro::Colonna::operator= (const Colonna& col)
{
  if (this != &col)
    {
      distruggi();
      mattone *m;
      mattone *m1 = col.primo;
      for (nmattoni = 0; nmattoni < col.nmattoni; nmattoni++)
        {
	  ultimo = new mattone;
	  ultimo->c = m1->c;
	  (nmattoni == 0) ? primo : m->next = ultimo;
	  m = ultimo;
	  m1 = m1->next;
	}
    }
  return *this;
}
void 
Muro::Colonna::allunga (colore c)
{
  ultimo = ultimo->next = new mattone;
  ultimo->c = c;
  nmattoni++;
}
void 
Muro::Colonna::accorcia ()
{
  if (nmattoni == 0)
    errore ("colonna vuota");
  if (nmattoni == 1)
    delete primo;
  else
    {
      ultimo = primo;
      for (int i = 0; i < nmattoni - 2; i++)
	ultimo = ultimo->next;
      delete ultimo->next;
    }
  nmattoni--;
}
Muro::Muro (int n)
{
  if (n < 1)
    errore ("Un muro deve avere almeno una colonna");
  muro = new Colonna [ncolonne = n]; 
}
void
Muro::copia (const Muro& m)
{
  ncolonne = m.ncolonne;
  muro = new Colonna [ncolonne];
  for (int i = 0; i < ncolonne; i++)
    muro[i] = m.muro[i];
}
Muro::Colonna&
Muro::alta () const
{
  int indice = 0;
  int max = muro[0].altezza();
  for (int i = 1; i < ncolonne; i++)
    if (muro[i].altezza() > max)
      { indice = i; max = muro[i].altezza(); }
  return muro[indice];
}
Muro::Colonna&
Muro::bassa () const
{
  int indice = 0;
  int min = muro[0].altezza();
  for (int i = 1; i < ncolonne; i++)
    if (muro[i].altezza() < min)
      { indice = i; min = muro[i].altezza(); }
  return muro[indice];
}
inline Muro& 
Muro::operator= (const Muro& m) 
{ 
  if (&m != this)
    {
      delete [] muro;
      copia(m); 
    }
  return *this;
}
ostream&
operator<< (ostream& os, const Muro& m)
{
  for (int i = 0; i < m.ncolonne; i++)
    {
      os << '[';
      Muro::Colonna::mattone *matt = m.muro[i].primo;
      for (int j = 0; j < m.muro[i].nmattoni; j++)
	{
	  if (j > 0)
	    os << ' ';
	  switch (matt->c)
	    {
	    case BIANCO: os << 'B'; break;
	    case ROSSO: os << 'R'; break;
	    case GIALLO: os << 'G'; break;
	    case VERDE: os << 'V'; break;
	    }
	  matt = matt->next;
	}
      os << ']';
    }
  return os;
}

Sotto egcs compila regolarmente, mentre sotto VC++ da` errore:
Mi pare, a occhio, che tu abbia ragione: operator<<(ostream&,const Muro&),
essendo dichiarato friend di Muro::Colonna, dovrebbe infatti avere accesso
a tutto quanto e` "private" nella classe in questione.  Apparentemente, VC++
si lascia confondere dall'interazione fra classi annidate e funzioni globali
friend; il bug (secondo me e` un bug) esiste anche in VC++6.0.
Alex

Confermo,
ho dovuto spostare un mebro da private a public per lo stesso motivo.
byebyte

Ho fatto  un'indagine su comp.std.c++ ed  ho ottenuto dei  seguenti che qui
riassumo riguardo al  problema che avevo proposto.  Il  problema era che un
codice che  appare perfettamente corretto  genera una condizione  di errore
fasulla in Visual  C++ dalla 4.0 alla 6.0.  Questo e`  il codice e l'errore
generato  (per Alex Martelli:  questo codice  e` diverso  da quello  che tu
avevi suggerito, vedi seguito):
================================================================
class A {
  class B {
	typedef int C;
    friend void f();
  };
  friend void f();
};
void f() {
  A::B::C *p;
}
// baco2_VC.cpp(10) : error C2248: 'C' :
//   cannot access private typedef declared in class 'A::B'
================================================================
E` opinione comune  che il codice sia in effetti  corretto, ed infatti esso
compila  correttamente  su  tutti  i  compilatori  provati  da  chi  si  e`
interessato della cosa.  Il motivo  per cui VC++ si rifiuti di considerarlo
corretto e` ignoto.
Qualcuno sa se sia il caso di segnalare l'errore alla MS?  Intendo dire, se
qualcuno  alla MS si  prenderebbe la  briga di  leggere la  segnalazione di
errore e fare qualcosa?
Come effetti collaterali della discussione su comp.std.c++ si sono avute le
seguenti due osservazioni:
1) Alcuni  sostenevano  che  solo   la  prima  dichiarazione  friend  fosse
   necessaria.  Il  consenso comune finale, supportato dai  risultati di un
   paio di  compilatori provati e  da qualche citazione dello  standard, e`
   invece  che  siano  necessarie  entrambe:  la prima  per  permettere  la
   visibilita` di C, la seconda per permettere la visibilita` di B.
2) Il  compilatore egcs,  che molti  considerano uno  dei piu`  vicini allo
   standard, puo` fare a meno  di entrambe le dichiarazioni friend!  Questo
   e`  un baco,  la  cui  origine e`  dovuta  al fatto  che  ancora non  e`
   implementato il controllo di accesso alle classi annidate, secondo Jason
   Merrill della Cygnus.
Grazie  ad  Alex Martelli  per  avermi  indirizzato  sulla giusta  via  per
risolvere questo problema.

errore e fare qualcosa?
Spedisci il baco a Ron Burk, che cura la rubrica "Bug++ of the Month"
sul Windows Developer Journal, 
wdle...@mfi.com
 -- se ti accetta la
segnalazione vinci una T-Shirt, e la probabilita` che Microsoft (e Borland)
riparino un qualche bug cresce se vengono pubblicamente svergognate
in merito su di una rivista diffusa (si beccano in media una svergognata
mostruosa a testa al mese, penso che ne correggano un 10-15%, certo
piu` di quelle che correggono sulla base di segnalazioni "private"...:-).
Gli ultimi bachi cosi` evidenziati di recente...: su VC++, se un oggetto
globale e` dichiarato extern nello stesso sorgente dove e` definito, il
suo costruttore e` eseguito due volte (Microsoft ammette il bug, promette
futuri fix, e come workaround temporaneo si raccomanda di non avere
una extern per un oggetto globale nel sorgente dove e` definito...); su
C++ Builder 1, o 3, AnsiString NON gestisce bene il reference count --
modificando una copia con l'operator[], si sporca anche l'originale
(Inprise ammette il bug, e come workaround suggerisce di chiamare
Unique() su di una AnsiString prima di un operator[] che puo` cambiarla,
o entro l'operator[] stesso -- purtroppo, questo da` parecchio overhead).
(Curioso come Herb Sutter abbia dedicato TANTO tempo il mese scorso,
su comp.lang.c++.moderated, a mostrare che scrivere una stringa 
reference-counted, copy-on-write NON e` banale -- e Herb pensava
solo a bachi e overhead per i casi _multi-threaded_...!-).
Alex

   visibilita` di C, la seconda per permettere la visibilita` di B.
Non utilizzando il vc++, non ho seguito il thread... comunque ho fatto la
prova con il borland C++ 5.02 e in effetti confermo quanto dici.
Un risultato comunque che ci si doveva aspettare... la dichiarazione di una classe B all'interno di un'altra
(diciamo A), dice semplicemente che B puo` essere vista solo attraverso A. Questo pero` non autorizza
A ad accedere ai membri privati di B. A maggior ragione quindi una funzione esterna ad A, per quanto
dichiarata friend di A, puo` essere autorizzata ad accedere ai campi privati di B; ci deve essere
una esplicita autorizzazione in B.
Per sicurezza, ho cercato anche sul documento ANSI base (non ho sottomano lo standard finale, ma
dovrebbe esserci sostanziale identita` di vedute), e in effetti in merito dice:
"Member functions of a nested class have no special access to members of an enclosing class;
they obey the usual access rules. Member functions of an enclosing class have no special access to members
of a nested class; they obey to the usual access rules."
che tradotto (per chi non mastica l'inglese) suona (piu` o meno) cosi`:
"Le funzioni membro di una classe annidata non hanno alcuno speciale (diritto di) accesso ai membri
della classe che la racchiude; essi obbediscono alle usuali regole di accesso. Le funzioni membro di
una classe che ne racchiude un'altra non hanno alcuno speciale (diritto di) accesso ai membri
di una classe annidata; essi obbediscono alle usuali regole di accesso."
Paolo
----------------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
  e-mail: 
mar...@cli.di.unipi.it
 
----------------------------------------------------------