Sobrecarga de funciones

La sobrecaga de funciones permite, básicamente, varias versiones de una misma función. Esto puede ser útil cuando deseamos aplicar la misma operación a distintos tipos de datos:

void escribe(int i)
{
printf( "0%5d", i );
}

void escribe(const string &s)
{
printf( "\"%s\"", s.c_str() );
}

Las funciones sobrecargadas deben diferenciarse, bien el número de los parámetros, o bien por el tipo de los parámetros, o incluso por ambas razones a la vez. Nunca por el tipo de retorno, que es indiferente a estos efectos.
La sobrecarga de funciones es también aplicable a los métodos de las clases, constructores incluídos. Por ejemplo, supongamos una clase Precio, capaz de construirse con el precio en pesetas o euros.

class Precio {
private:
    double precio;
public:
    static const double ValorEuroPts = 166.386;    

    Precio(int precioPts)
        { precio = precioPts / ValorEuroPts; }
    Precio(double precioEur)
        { precio = precioEur; }

    double getPrecio() const
        { return precio; }
    int getPrecioPts() const
        { return precio * ValorEuroPts; }
};

Nótese que mientras el constructor puede ser sobrecargado, puesto que lo que cambia es precisamente el tipo del argumento, getPrecio() no puede ser sobrecargado, pues sólo cambia el tipo de retorno.

Sobrecarga de operadores

Para la sobrecarga de operadores, en realidad se emplea la sobrecarga de funciones, de manera que un operador no es más que una pequeña capa de "azucar sintáctico". Por ejemplo, para una clase que represente las cadenas:

Cadena c1( "Hola" );
c1 += ", mundo"; // Equivale a c1.operator+=( ", mundo" );

De hecho, se puede comprobar que esa segunda forma de llamar al operador (como a un método), compila perfectamente. Así, se trata de sobrecargar métodos dentro de una clase:

class Cadena {
private:
char * cad;
void copiar(const string &s)
{ delete cad;
cad = new char[ s.length() + 1 ];
strcpy( cad, s.c_str() );
}
void concatenar(const string &s)
{
// ... más cosas ...
}
// ... más cosas ...
public:
// ... más cosas ...
Cadena(const string &s = "")
{ cad = NULL; copiar( s ); }
Cadena(const Cadena &c)
{ cad = NULL; copiar( s ); }

Cadena &operator+=(const string &s)
{ concatenar( s ); return *this; }
Cadena &operator+=(const Cadena &s)
{ concatenar( s.getCadena() ); return *this; }

Cadena &operator=(const Cadena &s)
{ copiar( s.getCadena() ); return *this; }
Cadena &operator=(const string &s)
{ copiar( s ); return *this; }

const char * getCadena() const
{ return cad; }
};

int main()
{
Cadena c1( "Hola" );
Cadena c2( c1 );

c1 += ", mundo";
c2 = c1;
c2 = "LP";

cout << c1.getCadena() << endl;
}
Este es el caso de operadores que modifican al objeto que está a la izquierda de los mismos (=, +=). También hemos creado un constructor de copia para poder estar a salvo de los problemas de compartición de memoria vistos anteriormente. Estos operadores modifican al objeto, por lo que lo devuelven como una referencia (que es equivalente a un puntero, excepto en la sintaxis). Ya que el objeto va a sobrevivir a la ejecución del método (operator+=() y operator=()), se devuelve como una referencia para evitar la alternativa, la devolución por valor, que supondría la copia del objeto. Así, de esta manera es más eficiente.
La razón de devolver el propio objeto es que, así, se pueden encadenar las asignaciones:

cad1 = cad2 = cad3 += ".\n";

... que se van anidando de derecha a izquierda.

Sobrecarga de operadores condicionales

También es posible sobrecargar operadores condicionales(<,>,=,!=). Por ejemplo, se podrían añadir los siguientes a la clase Cadena:

bool operator==(const Cadena &c)
{ return ( strcmp( cad, c.cad ) == 0 ); }
bool operator==(const string &s)
{ return ( strcmp( cad, s.c_str() ) == 0 ); }

Los operadores condicionales tienen la única peculiaridad de devolver verdadero o falso.

Operadores que no modifican al objeto

Hasta ahora hemos visto operadores que modifican el objeto que está a su izquierda, pero a veces es necesario devolver, símplemente, un nuevo objeto. Por ejemplo, el operador + se distingue del += en que se crea un nuevo objeto con el resultado de la concatenación, y no se espera que se modifique el objeto.

Cadena operator+(const Cadena &c)
{ return Cadena( string( c.cad ).concat( cad ) ); }
Cadena operator+(const string &s)
{ return Cadena( s.concat( cad ) ); }

Nótese que se devuelve un nuevo objeto, creado a partir de concatenar la cadena propia (la del objeto que está a la izquierda, y la del parámetro (el objeto a la derecha).

Debido a que es un nuevo objeto que no sobrevivirá a la terminación del método operator+, es necesario devolverlo por valor.
int main()
{
Cadena c1( "Hola, " );
Cadena c2( "mundo." );
Cadena c3;

// c1 y c2 no se modifican
c3 = c1 + c2; // c3.operator=(c1.operator+(c2));

cout << c3.getCadena() << endl;
}

Operadores que no tienen un objeto de nuestra clase a la izquierda

Sería interesante poder sobrecargar los operadores '>>' y '<<' para poder leer y escribir el contenido de un objeto de la clase Cadena en cualquier flujo. Sin embargo, nos encontramos con el problema de que a la izquierda de los operadores están cout y cin, y no un objeto de la clase Cadena. Para ello, es necesario sobrecargar el operador << para ambos parámetros, de manera que C++ sepa en qué casos se aplica. El método será una función friend lo que significa que tiene acceso a la parte privada de los objetos de la clase donde esá definido. Aunque no es parte de la clase, está mejor encapsulado si se introduce en su interior.

friend ostream &operator<<(ostream &o, const Cadena &c)
{ o << c.getCadena(); return o; }
friend istream &operator>>(istream &i, const Cadena &c)
{ string s; getline( i, s );
copiar( s ); return i };
Ahora se puede escribir:
int main()
{
Cadena c1 = "Hola, "; // Constructor, no operador =
Cadena c2;

cout << "Dime tu nombre: ";
cin >> c2;
cout << c1 << c2 << endl;
}

Clases amigas
Las clases amigas son muy parecidas a las funciones amigas: los métodos de una clase que es declarada como amiga de una dada, pueden acceder a la parte privada de esa última. Una clase no puede indicar ser amiga de otra, y acceder a su parte privada, sino al revés. Sólo las clases que otorgan este privilegio pueden ser accedidas por las clases a las que se les ha otorgado esa confianza (es una relación en una sola dirección).


class Producto {
public:
	friend class Factura;
	// ... más cosas ...
};

class Factura {
public:
	// ... más cosas ...
	 Factura(const Proveedor &p, const Producto &p);
	 void generar()
		{
cout << "Factura: " << producto.nombre
                       	     << '\t' << producto.precio;
		}
};

Sobrecarga de operadores avanzados

Los autopunteros son una posibilidad incluída en C++ desde el estándar de 1998. Se trata de poder utilizar objetos alojados en el heap, sin tener que recordar eliminarlos más adelante.
	#include <memory>
#include <cstdio>
#include <cstdlib>

int main()
{
std::auto_ptr i( new int );

*i = 5;
std::printf( "%d\n", *i );

return EXIT_SUCCESS;
}
Normalmente, este programa implicaría una fuga de memoria (memory leak), pues no se está liberando el objeto reservado con el operador new. La ventaja del autopuntero está precisamente en que, cuando se elimina el mismo, hace un delete automáticamente del objeto apuntado (auto_ptr no es más que una clase, en este caso se utiliza su destructor). Una limitación menor de auto_ptr es que no se puede utilizar la sintaxis '=' para la construcción. Es decir, no se puede compilar "std::auto_ptr i = new int;", simplemente no funcionará. Una limitación mayor de auto_ptr es que no se puede utilizar sobre vectores. Tal y como se ha explicado anteriormnte, existen dos operadores para eliminar vectores, delete y delete[], y auto_ptr no puede elegir automáticamente el más conveniente.

Un ejemplo un poco más útil de auto_ptr cuando se emplea una clase:

	#include <memory>
#include <cstdio>
#include <cstdlib>
#include <cstring>

class Cadena {
public:
Cadena(char * str = NULL) : s( NULL )
{ guardar( str ); }
Cadena(const Cadena &c)
{ guardar( c.get() ); }
~Cadena()
{ delete s; }
Cadena &operator=(const Cadena &c)
{ guardar( c.get() ); return * this; }
char * get()
{ return s; }
const char * get() const
{ return s; }
private:
char * s;

void guardar(const char *str)
{ delete s;
if ( str != NULL ) {
s = new char[ std::strlen(str) +1 ];
std::strcpy( s, str );
}
}
};

int main()
{
std::auto_ptr str( new Cadena( "Hola" ) );
std::auto_ptr str2( new Cadena( *str ) );

std::printf( "%s\n", str->get() );

return EXIT_SUCCESS;
}
Tal y como se puede apreciar en el ejemplo, es posible emplear el operador '->' y el operador '*' normalmente, tal y como se haría con cualquier puntero normal. De hecho, la única forma de acceder a los métodos de la propia clase auto_ptr es utilizar el operador '.', el único de los operadores de este nivel que no es sobrecargable.

auto_ptr proporciona los siguientes métodos:
getDevuelve el puntero almacenado.
releaseLibera al autopuntero de liberar el objeto, y se resetea.
resetLibera el objeto apuntado y pasa a apuntar a otro.
operator=Hace un reset en el objeto a la izquierda de =, y un release en el de la derecha

Así, si es necesario comprobar que, por ejemplo, el puntero almacenado no es NULL, tendría que hacerse:

	if ( str.get() != NULL ) {
// más cosas...
}
release libera al auto puntero de todas sus obligaciones:
	int main()
{
std::auto_ptr str( new Cadena( "Hola" ) );
Cadena * str2;

str2 = str.get();
str.release();

std::printf( "%s\n", str2->get() );

delete str2;
return EXIT_SUCCESS;
}
Con reset, primero se hace un release del objeto actual, y se almacena otro nuevo:
	int main()
{
std::auto_ptr str( new Cadena( "Hola" ) );

str.reset( new Cadena( "adiós" ) );

std::printf( "%s\n", str->get() );

return EXIT_SUCCESS;
}
Finalmente, el operador '=' aplicado sobre un autopuntero, hace que la responsabilidad de liberar el objeto apuntado por el objeto a la derecha del '=' pase al de su izquierda, y sobre el objeto de la derecha ya mencionado se efectúe un release.
	int main()
{
std::auto_ptr str2( new Cadena( "Hola" ) );
std::auto_ptr str;

str = str2;

std::printf( "%s\n", str->get() );

return EXIT_SUCCESS;
}

Con el operador de copia de auto_ptr sucede lo mismo; esto es útil para el caso del retorno de las funciones, en las que se efectúan varias copias.

	std::auto_ptr crearCadena(const char *str)
{
return std::auto_ptr( new Cadena( str ) );
}

int main()
{
std::printf( "%s\n", crearCadena( "Hola" )->get() );

return EXIT_SUCCESS;
}
Los autopunteros son una posibilidad muy cómoda en la librería de C++, pero es que no es menos cierto que constituyen, junto con el resto de punteros inteligentes (smart pointers), una de esas pocas situaciones en las que está justificado sobrecargar los operadores * y ->, así, auto_ptr es una clase relativamente simple que puede crearese así:
template <typename T>
class AutoPtr {
public:
AutoPtr(T * p = NULL) : ptr( NULL )
{ reset( p ); }
AutoPtr(AutoPtr &a)
{ this->reset( a.get() ); a.release(); }
~AutoPtr()
{ release(); }

T * get()
{ return ptr; }

const T * get() const
{ return ptr; }

T * operator ->()
{ return ptr; }
T &operator *()
{ return *ptr; }

void reset(T * p)
{ release(); ptr = p; }
void release()
{ delete ptr; ptr = NULL; }

AutoPtr &operator=(AutoPtr &a)
{ if ( &a == this ) {
this->reset( a.get() ); a.release();
}
return * this;
}

private:
T * ptr;
};
Otros posibles punteros inteligentes son aquellos que emplean medidas más sofisticadas, como el conteo de referencias, etc.