EC++: new/delete vs. new[]/delete[]

Appunti tratti dalla rilettura di Effective C++ di Scott Meyers. Secondo blocco: Gestione della memoria.

(5) Usa la stessa forma nei corrispondenti new e delete.

Le due forme degli operatori per la gestione della memoria sono relative alla gestione di un singolo oggetto o di un array (usando le parentesi quadre).

Quando si alloca un array di oggetti, usando la forma new[], occorre deallocarli usando la corrispondente delete[], altrimenti si causerà un comportamento indefinito del nostro codice.

Questo il modo corretto di allocare/deallocare memoria per un singolo oggetto e per un array di oggetti:

string* pString = new string;
string* strings = new string[100];

// ...

delete pString;
delete[] strings;

EC++: commenti

Appunti tratti dalla rilettura di Effective C++ di Scott Meyers. Primo blocco: passare dal C al C++.

(4) Meglio usare i commenti in stile C++.

Lo svantaggio dei commenti in stile C (/* ... */) é che si ingarbugliano nel caso di annidamenti.

I commenti C++ (// ...) hanno il vantaggio di valere da quel punto fino al termine della riga.

Ci sono evidentemente eccezioni. Ad esempio commentare una define in questo modo:

#define LIGHT_SPEED 3e8 // m/sec (in a vacuum)

é una richiesta di problemi, dato che il preprocessore sostituisce tutto quello che segue LIGHT_SPEED nel codice sorgente, introducendo il commento in modo quasi certamente inaspettato.

EC++: new e delete

Appunti tratti dalla rilettura di Effective C++ di Scott Meyers. Primo blocco: passare dal C al C++.

(3) Meglio usare new e delete invece di malloc e free.

Un problema é che malloc e free non sanno nulla di costruttori e distruttori, si occupano solo dell'allocazione e rilascio della memoria.

É evidentemente una pessima idea usare nello stesso codice entrambi i modelli per la gestione della memoria.

EC++: iostream

Appunti tratti dalla rilettura di Effective C++ di Scott Meyers. Primo blocco: passare dal C al C++.

(2) L'uso di iostream é da prefersi rispetto a stdio.h

La gestione dello standard i/o del C é eccellente ma non é object-oriented. In particolare scanf e printf non sono type-safe e non sono estensibili.

Meglio usare cin, cout, cerr e gli operatori >> e <<.

EC++: const

Appunti tratti dalla rilettura di Effective C++ di Scott Meyers. Primo blocco: passare dal C al C++.

(1) const e inline sono da prefersi a #define

Il motivo é che #define é una direttiva al preprocessore, meglio invece é appoggiarsi sul compilatore, quando possibile, in modo da avere un miglior supporto nel debugging.

In pratica, invece di scrivere:

#define ASPECT_RATIO 1.653

É meglio scrivere:

const double ASPECT_RATIO = 1.653;

Ci sono alcune sottigliezze su questo punto.

La prima riguarda i puntatori. In questo caso const può essere usato per fare in modo che sia il puntatore sia il puntato siano costanti. Quindi se vogliamo che valgano entrambe le restrizioni dobbiamo usare const due volte:

const char * const authorName = "Scott Meyers";


La definizione di una costante all'interno di una classe é una faccenda un po' più complessa.

Per prima cosa la costante deve essere dichiarata come static:

class Something {
// ...
static const int index = 12;
};

E poi deve essere definita:

const int Something::index;

In alternativa, per costanti intere, é sempre possibile ripiegare sull'uso di un enumeratore:

class Something {
// ...
enum { INDEX = 5 };
};

Per quanto riguarda l'altro tipico uso della direttiva #define, la creazione di macro, il C++ mette a disposizione la possibilità di risolvere inline una funzione, eliminando il costo della chiamata ma mantenendo la sicurezza dei controlli forniti dal compilatore. Per ottenere l'effetto di avere un singolo blocco di codice valido per i diversi tipi di dato, abbiniamo la specifica inline al template, usando le possibilità offerte dalla programmazione generica in C++. Il risultato é che la nota macro del C per determinare il maggiore tra due valori:

#define max(a,b) ((a) > (b) ? (a) : (b))

Viene riscritta in C++ in questo modo:

template
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; }

Scott Meyers

Un autore che difficilmente manca nella libreria di chi si occupi di sviluppo software in C++ é Scott Meyers.

Per quanto mi riguarda sono l'orgoglioso possessore di una copia della prima edizione di Effective C++ (l'orgoglio é mitigato dal fatto che c'é una lunga serie di correzioni e postille che vanno applicate al testo per renderlo aggiornato), e una copia di More Effective C++ che ogni tanto rivedo.

Non ho ancora affrontato Effective STL, ma penso che prima o poi finirò per cedere al fascino di fare tris.

STL - find e find_if

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che non modificano le sequenze su cui operano.

Ci sono due versioni dell'algoritmo di ricerca, a seconda se si voglia specificare o meno un predicato da applicare alla ricerca.

La find() cerca il primo elemento nella sequenza che sia uguale al parametro passato, la find_if() cerca il primo che soddisfi il predicato passato come parametro.

Ecco l'esempio che mostra l'uso di find_if():

#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;

void display(int x) {
cout << x << ' ';
}

class odd {
public:

bool operator()(int x) { // odd argument yields true
return x % 2;
}
};

int main() {
vector<int> v(8);
for (size_t i = 0; i < v.size(); ++i)
v[i] = 2 * i; // all even
v[5] += 1; // an odd number

// display
for_each(v.begin(), v.end(), display);
cout << endl;

// search for odd number
vector<int>::iterator it = find_if(v.begin(), v.end(), odd());
if (it != v.end()) {
cout << "The first odd number (" << *it << ") was found at position "
<< (it - v.begin()) << "." << endl;
}
else
cout << "No odd number found." << endl;
}

STL - for_each

Dal quinto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Algoritmi standard.

Sezione dedicata agli algoritmi che non modificano le sequenze su cui operano.

L'algoritmo for_each() opera non modifica direttamente la sequenza su cui opera, ma la funzione, o il functor (function object), che accetta come parametro può farlo.

A seguire un esempio che illustra l'uso di for_each sia con una funzione che con un functor.

#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;

static void display(int x) {
cout << x << ' ';
}

class Increment { // functor class
private:
int increment;

public:

Increment(int i = 1) : increment(i) {
}

void operator()(int& x) { // mutating operator
x += increment;
}
};

int main() {
vector<int> v(5); // vector of 5 zeros

for_each(v.begin(), v.end(), display); // 0 0 0 0 0
cout << endl;

// v is changed by the functor, not by for_each:
for_each(v.begin(), v.end(), Increment(2));


for_each(v.begin(), v.end(), display); // 2 2 2 2 2
cout << endl;
}

STL - map e multimap

Dal quarto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Parte dedicata ai container associativi ordinati.

Il container associativo map gestisce collezioni di dati acceduti per mezzo di una chiave, secondo la quale sono ordinati gli elementi.

Questo é un piccole esempio d'uso per map:

#include<map>
#include<string>
#include<iostream>

using namespace std;

typedef map<int, string> MyMap;
typedef MyMap::value_type MyValue;
typedef pair<map<int, string>::iterator, bool> InsRet;

int main() {
MyMap myMap;
InsRet ret = myMap.insert(MyValue(836361136, "Andrew"));
if(ret.second == true) {
cout << (*(ret.first)).second << " inserted." << endl;
}
ret = myMap.insert(MyValue(274635328, "Berni"));
if(ret.second == true) {
cout << (*(ret.first)).second << " inserted." << endl;
}
ret = myMap.insert(MyValue(260736622, "John"));
if(ret.second == true) {
cout << (*(ret.first)).second << " inserted." << endl;
}
ret = myMap.insert(MyValue(720002287, "Karen"));
if(ret.second == true) {
cout << (*(ret.first)).second << " inserted." << endl;
}
ret = myMap.insert(MyValue(138373498, "Thomas"));
if(ret.second == true) {
cout << (*(ret.first)).second << " inserted." << endl;
}
ret = myMap.insert(MyValue(135353630, "William"));
if(ret.second == true) {
cout << (*(ret.first)).second << " inserted." << endl;
}
ret = myMap.insert(MyValue(720002287, "Xaviera"));
if(ret.second == false) {
cout << "As expected, item not inserted: key duplicated" << endl;
}

myMap[420602587] = "Willie";
myMap[420602587] = "Willie Nillie";

cout << endl << "The map sorted by key:" << endl;

for(MyMap::iterator it = myMap.begin(); it != myMap.end(); ++it) {
cout << (*it).first << ':' << (*it).second << endl;
}

cout << endl << "Finding an element by key: ";
int key = 720002287;
MyMap::iterator it = myMap.find(key);
if(it != myMap.end()) {
cout << key << " " << (*it).second << endl;
}

cout << "Same, using array notation: ";
cout << key << " " << myMap[key] << endl;
}

La multimap permette di avere chiavi duplicate, questo porta a rendere impossibile implementare l'operatore []. Inoltre, in modo simile a quanto visto per la coppia di classi set/multiset, anche per multimap il metodo insert() ritorna un iteratore all'elemento appena inserito.

STL - set e multiset

Dal quarto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Parte dedicata ai container associativi ordinati.

Un set é una collezione di elementi distinti. Non ci possono dunque essere due elementi uguali in una collezione. Nella implementazione STL gli elementi sono ordinati anche se questo non sarebbe strettamente necessario, se non viene specificato un ordinatore, viene usato come default less<T>.

Un esempio d'uso di set:

#include<iostream>
#include<set>

using namespace std;

/**
* check the pair returned by an insert operation on set
*/
static void checkInsertResult(pair<set<int>::iterator, bool> res) {
cout << "Insertion ";
if(res.second == false)
cout << "not ";
cout << "performed";
if(res.second == true)
cout << " for " << *(res.first);
cout << endl;
}

int main() {
set<int> mySet;

// inserting 10 elements
for(int i = 0; i < 10; ++i)
checkInsertResult(mySet.insert(i));

// trying to insert an element twice does not succeed
checkInsertResult(mySet.insert(4));

// dump
set<int>::iterator it = mySet.begin();
for(it = mySet.begin(); it != mySet.end(); ++it)
cout << *it << ' ';
cout << endl;

// looking for an element that is not there
it = mySet.find(12);
if(it == mySet.end())
cout << "Can't find element 12 (as expected)" << endl;

// checking for an element in the set
if(mySet.count(4) == 1)
cout << "The element 4 is in the set" << endl;

// find and remove an element
it = mySet.find(4);
if(it != mySet.end()) {
mySet.erase(it);
cout << "The element 4 erased" << endl;
}

// erase an elment directly
if(mySet.erase(6) == 1)
cout << "The element 6 erased" << endl;
if(mySet.erase(6) == 0)
cout << "Can't erase element 6 (as expected)" << endl;

// dump
it = mySet.begin();
while(it != mySet.end())
cout << *it++ << ' ';
cout << endl;
}

Esiste anche la classe multiset, che permette l'esistenza di elementi ripetuti; l'interfaccia é praticamente identica a quella di set, con la differenza sostanziale del metodo insert() che ritorna, in caso di successo, l'iteratore che punta all'elemento inserito nel container.

Set ordinati e no

Da Head First Java O'Reilly, capitolo 16 che tratta collezioni e programmazione generica.

Abbiamo detto che gli elementi di un Set sono unici. Ma dobbiamo specificare cosa si intende essere unico per un elemento.

Due oggetti sono considerati uguali in Java se vale one.equals(two) e one e two hanno il medesimo codice hash, come ritornato dal metodo hashcode(). Perché Set consideri un oggetto come duplicato dobbiamo ridefinire hashCode() e equals() in modo che i due oggetti siano visti come identici.

Per vedere se due reference puntino allo stesso oggetto possiamo usare l'operatore ==.

Dunque, se vogliamo poter usare la classe Song che abbiamo visto in questo post in un Set dobbiamo ridefinire il modo in cui due oggetti di tipo Song sono riconosciuti come identici. Aggiungeremo perciò la ridefinizione per il metodo equals() e hashCode() in questo modo:

public class Song implements Comparable<Song> {

private String title;
private String artist;
private int rating;

// ...

@Override
public boolean equals(Object o) {
if(o instanceof Song) {
Song s = (Song) o;
return s.title.equals(title) && s.artist.equals(artist);
}
return false;
}

@Override
public int hashCode() {
return title.hashCode() + artist.hashCode();
}

// ...
}

Data questa classe Song, possiamo riscrivere il nostro JukeBox per fare in modo che accetti in input una lista di brani non unici ma la collezione risultante contenga solo elementi unici.

Dobbiamo di nuovo prendere la decisione se vogliamo che la nostra collezione sia ordinata o meno. Se non siamo interessati all'ordinamento ci conviene usare HashSet, altrimenti TreeSet ci permette di mantenere la nostra collezione ordinata, al costo di un leggero aggravio dei tempi d'uso.
Nell'esempio che segue usiamo entrambi gli approcci:

package Chap16;

import java.util.*;

public class JukeBoxC {
private HashSet<Song> hs = new HashSet<Song>();
private TreeSet<Song> ts = new TreeSet<Song>();

public JukeBoxC() {
getSongs();
System.out.println(hs);
System.out.println(ts);
}

private void getSongs() {
// fake implementation
String[] fakeList = {"Pink Moon/Nick Drake/5", "Somersault/Zero 7/4",
"Shiva Moon/Prem Joshua/5", "Circles/BT (Brian Wayne Transeau)/3",
"Deep Channel/Afro Celts/3", "Passenger/Headmix/4",
"Pink Moon/Nick Drake/5", "Listen/Tahiti 80/3", "Listen/Tahiti 80/3"};
for(String s : fakeList) {
System.out.println(s);
String[] tokens = s.split("/");

int rating = Integer.parseInt(tokens[2]);
Song aSong = new Song(tokens[0], tokens[1], rating);
if(hs.add(aSong) == false) {
System.out.println("Duplicated song discarded");
}
ts.add(aSong);
}
}

public static void main(String[] args) {
new JukeBoxC();
}
}

Collezioni in Java

Da Head First Java O'Reilly, capitolo 16 che tratta collezioni e programmazione generica.

I principali tipi di collezioni resi disponibili in Java sono:
  • List: quando l'accento é sulla posizioni nella collezione. É possibile avere elementi ripetuti.
  • Set: quando vogliamo che gli elementi inclusi siano unici.
  • Map: quando si una chiave di accesso (univoca) agli elementi (che possono essere duplicati) nella collezione.
Se guardiamo la struttura delle classi e interfacce, scopriamo una ambiguità. Gran parte delle collezioni implementano, direttamente o indirettamente l'interfaccia Collection. Alcune però implementano l'interfaccia Map che é indipendente dalla gerarchia basata su Collection.

Le principali interfacce e classi definite in questo contesto sono:

Collection
Set [implementata da HashSet e LinkedHashSet], List [implementata da ArrayList, LinkedList e Array]
SortedSet [implementata da TreeSet]

Map [implementata da HashMap, LinkedHashMap e Hashtable]
SortedMap [implementata da TreeMap]