Share via


Clases C++ como tipos de valor

Las clases de C++ son, de forma predeterminada, tipos de valor. Se pueden especificar como tipos de referencia, lo que permite el comportamiento polimórfico para admitir la programación orientada a objetos. A veces, los tipos de valor se ven desde la perspectiva del control de la memoria y el diseño, mientras que los tipos de referencia se refieren a clases base y funciones virtuales con fines polimórficos. De forma predeterminada, los tipos de valor se pueden copiar, lo que significa que siempre hay un constructor de copias y un operador de asignación de copia. Para los tipos de referencia, haga que la clase no se puede copiar (deshabilite el constructor de copia y el operador de asignación de copia) y use un destructor virtual, que admite su polimorfismo previsto. Los tipos de valor están también relacionados con los contenidos que, cuando se copian, le proporcionan siempre dos valores independientes que se pueden modificar por separado. Los tipos de referencia están relacionados con la identidad: ¿qué tipo de objeto es? Por este motivo, los "tipos de referencia" también se conocen como "tipos polimórficos".

Si realmente desea un tipo similar a una referencia (clase base, funciones virtuales), tiene que deshabilitar explícitamente el copiado, como se muestra en la clase MyRefType en el código siguiente.

// cl /EHsc /nologo /W4

class MyRefType {
private:
    MyRefType & operator=(const MyRefType &);
    MyRefType(const MyRefType &);
public:
    MyRefType () {}
};

int main()
{
    MyRefType Data1, Data2;
    // ...
    Data1 = Data2;
}

La compilación del código anterior dará como resultado el siguiente error:

test.cpp(15) : error C2248: 'MyRefType::operator =' : cannot access private member declared in class 'MyRefType'
        meow.cpp(5) : see declaration of 'MyRefType::operator ='
        meow.cpp(3) : see declaration of 'MyRefType'

Tipos de valor y eficiencia de movimiento

Se evita la sobrecarga de asignación de copias debido a las nuevas optimizaciones de copia. Por ejemplo, cuando inserta una cadena en medio de un vector de cadenas, no hay ninguna sobrecarga de reasignación de copia, solo un movimiento, incluso si da como resultado el crecimiento del propio vector. Estas optimizaciones también se aplican a otras operaciones: por ejemplo, realizando una operación de adición en dos objetos enormes. ¿Cómo se habilitan estas optimizaciones de operaciones de valor? El compilador los habilita implícitamente, al igual que el compilador puede generar automáticamente los constructores de copia. Sin embargo, la clase tiene que "participar" en las asignaciones de movimiento y los constructores de movimiento declarándolos en la definición de clase. Move usa la referencia de valores de doble y doble (&&) en las declaraciones de función miembro adecuadas y define el constructor de movimiento y los métodos de asignación de movimiento. También debe insertar el código correcto para "vaciar" el objeto de origen.

¿Cómo decide si necesita habilitar las operaciones de movimiento? Si ya sabe que necesita habilitar la construcción de copia, probablemente quiera habilitar también la construcción de movimiento, especialmente si es más barato que una copia profunda. Sin embargo, si sabe que necesita compatibilidad con el movimiento, no significa necesariamente que quiera habilitar las operaciones de copia. Este último caso se denominaría "tipo de solo movimiento". Un ejemplo que ya está en la biblioteca estándar es unique_ptr. Como nota al margen, el antiguo auto_ptr está en desuso y fue reemplazado por unique_ptr precisamente debido a la falta de compatibilidad con la semántica de movimiento en la versión anterior de C++.

Mediante la semántica de transferencia de recursos, puede devolver por valor o insertar en medio. El movimiento es una optimización de la copia. No es necesaria la asignación de montón como solución alternativa. Considere el siguiente seudocódigo:

#include <set>
#include <vector>
#include <string>
using namespace std;

//...
set<widget> LoadHugeData() {
    set<widget> ret;
    // ... load data from disk and populate ret
    return ret;
}
//...
widgets = LoadHugeData();   // efficient, no deep copy

vector<string> v = IfIHadAMillionStrings();
v.insert( begin(v)+v.size()/2, "scott" );   // efficient, no deep copy-shuffle
v.insert( begin(v)+v.size()/2, "Andrei" );  // (just 1M ptr/len assignments)
//...
HugeMatrix operator+(const HugeMatrix& , const HugeMatrix& );
HugeMatrix operator+(const HugeMatrix& ,       HugeMatrix&&);
HugeMatrix operator+(      HugeMatrix&&, const HugeMatrix& );
HugeMatrix operator+(      HugeMatrix&&,       HugeMatrix&&);
//...
hm5 = hm1+hm2+hm3+hm4+hm5;   // efficient, no extra copies

Habilitación del movimiento para los tipos de valor adecuados

Para una clase similar a un valor en la que el movimiento puede ser más barato que una copia profunda, habilite la construcción de movimiento y la asignación de movimiento para mejorar la eficacia. Considere el siguiente seudocódigo:

#include <memory>
#include <stdexcept>
using namespace std;
// ...
class my_class {
    unique_ptr<BigHugeData> data;
public:
    my_class( my_class&& other )   // move construction
        : data( move( other.data ) ) { }
    my_class& operator=( my_class&& other )   // move assignment
    { data = move( other.data ); return *this; }
    // ...
    void method() {   // check (if appropriate)
        if( !data )
            throw std::runtime_error("RUNTIME ERROR: Insufficient resources!");
    }
};

Si habilita la construcción o asignación de copia, habilite también la construcción o asignación de movimiento si puede ser más barato que una copia en profundidad.

Algunos tipos que no son de valor son de solo movimiento, como cuando no se puede clonar un recurso, solo se transfiere la propiedad. Ejemplo: unique_ptr.

Consulte también

Sistema de tipos de C++
Aquí está otra vez C++
Referencia del lenguaje C++
Biblioteca estándar de C++