Regola del Tre, Polimorfismo e SequentialNetwork
La classe Matrix con dati privati e indicizzazione row-major:
Matrix::Matrix(int r, int c) : rows(r), cols(c) {
if (r <= 0 || c <= 0)
throw std::invalid_argument("Le dimensioni della matrice devono essere > 0");
data = new double[rows * cols];
for (int i = 0; i < rows * cols; ++i) data[i] = 0.0;
}
double Matrix::get(int r, int c) const {
if (r < 0 || r >= rows || c < 0 || c >= cols)
throw std::out_of_range("Indici di matrice fuori dai limiti!");
return data[r * cols + c]; // row-major: una 2D in un array 1D
}E il DenseLayer che invece delega la memoria a Eigen:
DenseLayer::DenseLayer(int input_size, int output_size) {
weights = Eigen::MatrixXd::Random(output_size, input_size);
biases = Eigen::MatrixXd::Zero(output_size, 1);
}
Eigen::MatrixXd DenseLayer::forward(const Eigen::MatrixXd& input) {
return (weights * input).colwise() + biases.col(0); // broadcasting
}Restava aperta la bomba: Matrix alloca con new[] e non ha distruttore. Oggi la disinneschiamo.
Due argomenti che si intrecciano:
Matrix e su SequentialNetwork.ILayer che permetta a DenseLayer e ai layer di attivazione di stare nello stesso contenitore.MatrixAggiungete a matrix.hpp i tre membri che mancano:
class Matrix {
private:
int rows, cols;
double* data;
public:
Matrix(int r, int c);
// ===== LA REGOLA DEL 3 =====
~Matrix(); // 1. Distruttore
Matrix(const Matrix& other); // 2. Costruttore di Copia
Matrix& operator=(const Matrix& other); // 3. Operatore di Assegnazione
double get(int r, int c) const;
void set(int r, int c, double val);
int getRows() const;
int getCols() const;
};// 1. Distruttore
Matrix::~Matrix() {
delete[] data;
}
// 2. Costruttore di Copia — DEEP copy: nuova memoria, dati clonati
Matrix::Matrix(const Matrix& other) : rows(other.rows), cols(other.cols) {
data = new double[rows * cols];
for (int i = 0; i < rows * cols; ++i)
data[i] = other.data[i];
}
// 3. Operatore di Assegnazione
Matrix& Matrix::operator=(const Matrix& other) {
if (this == &other) return *this; // a. auto-assegnazione: m1 = m1
delete[] data; // b. libera la vecchia memoria
rows = other.rows; // c. copia campi e rialloca
cols = other.cols;
data = new double[rows * cols];
for (int i = 0; i < rows * cols; ++i)
data[i] = other.data[i];
return *this; // d. riferimento a se stesso
}I quattro passi dell’operatore = vanno in quest’ordine: invertire (b) e (a) su m1 = m1 distrugge i dati che si stanno per copiare.
ILayerPerché la rete possa contenere layer di tipi diversi, serve una base comune. Crea ilayer.hpp:
#ifndef ILAYER_HPP
#define ILAYER_HPP
#include <Eigen/Dense>
class ILayer {
public:
// Distruttore virtuale: obbligatorio, si farà delete su puntatori a ILayer
virtual ~ILayer() = default;
// Virtuale puro: rende ILayer astratta (non istanziabile)
virtual Eigen::MatrixXd forward(const Eigen::MatrixXd& input) = 0;
// Idioma del "Costruttore Virtuale": serve alla Deep Copy polimorfica
virtual ILayer* clone() const = 0;
};
#endifPoi fate derivare DenseLayer da ILayer, aggiungendo override a forward e implementando clone():
Important
clone() non è opzionale: il costruttore di copia di SequentialNetwork non può funzionare senza. Da un ILayer* non si sa quale tipo concreto copiare — solo l’oggetto stesso lo sa.
SequentialNetwork — l’headerCrea sequential_network.hpp. Il contenitore è un array C-style dinamico: serve a farvi toccare i problemi che std::vector risolverà da solo.
#ifndef SEQUENTIAL_NETWORK_HPP
#define SEQUENTIAL_NETWORK_HPP
#include "ilayer.hpp"
#include <stdexcept>
class SequentialNetwork {
private:
ILayer** layers; // array dinamico di puntatori a ILayer
int capacity;
int size;
public:
SequentialNetwork(int max_layers);
// ===== LA REGOLA DEL 3 (di nuovo) =====
~SequentialNetwork();
SequentialNetwork(const SequentialNetwork& other);
SequentialNetwork& operator=(const SequentialNetwork& other);
void add(ILayer* layer);
Eigen::MatrixXd forward(const Eigen::MatrixXd& input);
int getSize() const;
};
#endifSequentialNetwork — l’implementazioneSequentialNetwork::SequentialNetwork(int max_layers)
: capacity(max_layers), size(0) {
if (capacity <= 0) throw std::invalid_argument("Capacity deve essere > 0");
layers = new ILayer*[capacity];
for (int i = 0; i < capacity; ++i) layers[i] = nullptr;
}
SequentialNetwork::~SequentialNetwork() {
for (int i = 0; i < size; ++i) delete layers[i]; // a. i layer
delete[] layers; // b. l'array
}
SequentialNetwork::SequentialNetwork(const SequentialNetwork& other)
: capacity(other.capacity), size(other.size) {
layers = new ILayer*[capacity];
for (int i = 0; i < size; ++i)
layers[i] = other.layers[i]->clone(); // clone POLIMORFICO
}
void SequentialNetwork::add(ILayer* layer) {
if (size >= capacity) throw std::out_of_range("Network capacity esaurita!");
layers[size++] = layer;
}
Eigen::MatrixXd SequentialNetwork::forward(const Eigen::MatrixXd& input) {
Eigen::MatrixXd current = input;
for (int i = 0; i < size; ++i)
current = layers[i]->forward(current); // ogni output è il prossimo input
return current;
}L’operator= segue lo stesso schema di Matrix: auto-assegnazione, libera, rialloca, clona. Attenzione che qui il “libera” è doppio: prima i layer, poi l’array.
#include <iostream>
#include <Eigen/Dense>
#include "dense_layer.hpp"
#include "sequential_network.hpp"
int main() {
Eigen::MatrixXd X = Eigen::MatrixXd::Random(784, 1);
SequentialNetwork net1(2);
// La rete fa delete sui puntatori: passiamo oggetti creati con new
net1.add(new DenseLayer(784, 128));
net1.add(new DenseLayer(128, 10));
Eigen::MatrixXd Z1 = net1.forward(X);
std::cout << "Rete 1:\n" << Z1.transpose() << std::endl;
SequentialNetwork net2 = net1; // <- Costruttore di Copia
Eigen::MatrixXd Z2 = net2.forward(X);
std::cout << "Rete 2 (copia):\n" << Z2.transpose() << std::endl;
}Il criterio di correttezza: Z1 e Z2 devono essere identici. I pesi sono casuali, quindi due reti costruite da zero darebbero numeri diversi: se coincidono, il clone() ha davvero duplicato i pesi.
Provate poi a commentare il copy constructor: il programma crasherà con un Double Free, perché entrambe le reti farebbero delete sugli stessi layer.
La soluzione di riferimento verrà discussa all’inizio della prossima lezione.