¿Tienen alguna utilidad las referencias de rvalue a const?

Resuelto fredoverflow asked hace 13 años • 8 respuestas

Supongo que no, pero me gustaría confirmarlo. ¿ Tiene algún uso dónde const Foo&&hay Fooun tipo de clase?

fredoverflow avatar Feb 09 '11 04:02 fredoverflow
Aceptado

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;
Howard Hinnant avatar Feb 09 '2011 02:02 Howard Hinnant

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_uniquey make_shared, pero ambos unique_ptry shared_ptrpueden 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_ptry shared_ptrpodrí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 ptraú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::movea 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 constcomo 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 .

Amir Kirsh avatar Mar 08 '2020 12:03 Amir Kirsh

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.

Gene Bushuyev avatar Feb 08 '2011 21:02 Gene Bushuyev