¿Tienen alguna utilidad las referencias de rvalue a const?
Supongo que no, pero me gustaría confirmarlo. ¿ Tiene algún uso dónde const Foo&&
hay Foo
un tipo de clase?
En ocasiones son útiles. El borrador C++ 0x los usa en algunos lugares, por ejemplo:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Las dos sobrecargas anteriores garantizan que las otras funciones ref(T&)
y cref(const T&)
no se vinculen a rvalues (lo que de otro modo sería posible).
Actualizar
Acabo de comprobar el estándar oficial N3290 , que lamentablemente no está disponible públicamente y tiene en 20.8 Objetos de función [function.objects]/p2:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Luego revisé el borrador más reciente posterior a C++ 11, que está disponible públicamente, N3485 , y en 20.8 Objetos de función [function.objects]/p2 todavía dice:
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
La semántica de obtener una referencia de valor constante (y no para =delete
) es para decir:
- ¡No admitimos la operación para valores l!
- aun así, todavía copiamos porque no podemos mover el recurso pasado o porque no hay ningún significado real para "moverlo".
En mi humilde opinión , el siguiente caso de uso podría haber sido un buen caso de uso para la referencia de rvalue a const , aunque el lenguaje decidió no adoptar este enfoque (consulte la publicación SO original ).
El caso: constructor de punteros inteligentes a partir de punteros sin formato
Normalmente sería recomendable utilizar make_unique
y make_shared
, pero ambos unique_ptr
y shared_ptr
pueden construirse a partir de un puntero sin formato. Ambos constructores obtienen el puntero por valor y lo copian. Ambos permiten (es decir, en el sentido de: no impiden ) un uso continuo del puntero original que se les pasó en el constructor.
El siguiente código se compila y da como resultado double free :
int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;
Ambos unique_ptr
y shared_ptr
podrían evitar lo anterior si sus constructores relevantes esperaran obtener el puntero sin formato como un valor constante , por ejemplo, para unique_ptr
:
unique_ptr(T* const&& p) : ptr{p} {}
En cuyo caso el doble código libre anterior no se compilaría, pero lo siguiente sí lo haría:
std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) }; // ok, rvalue
Tenga en cuenta que ptr
aún se podría utilizar después de moverlo, por lo que el error potencial no ha desaparecido por completo. Pero si se requiere que el usuario llame std::move
a dicho error, caería en la regla común de: no usar un recurso que fue movido.
Uno puede preguntar: OK, pero ¿por qué T*
const&& p
?
La razón es simple: permitir la creación de unique_ptr
un puntero constante . Recuerde que la referencia const rvalue es más genérica que solo la referencia rvalue , ya que acepta tanto const
como non-const
. Entonces podemos permitir lo siguiente:
int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };
esto no funcionaría si esperáramos solo una referencia de rvalue (error de compilación: no se puede vincular const rvalue a rvalue ).
De todos modos, ya es demasiado tarde para proponer tal cosa. Pero esta idea presenta un uso razonable de una referencia de valor a const .
Están permitidos e incluso las funciones se clasifican según const
, pero como no se puede mover desde el objeto constante al que hace referencia const Foo&&
, no son útiles.