7

Tengo una clase que 'recuerda' una referencia a algún objeto (por ejemplo, una variable entera). No puedo permitir que haga referencia a un valor que se destruye de inmediato, y estoy buscando una forma de proteger a los usuarios de mi clase de hacerlo por accidente.impedir el paso por ref del objeto temporal

¿Es una sobrecarga de referencia rvalue una buena forma de evitar que se pase un temporal?

struct HasRef { 
    int& a; 
    HasRef(int& a):a(a){} 
    void foo(){ a=1; } 
}; 


int main(){ 
    int x=5; 
    HasRef r1(x); 
    r1.foo(); // works like intended. 

    HasRef r2(x+4); 
    r2.foo(); // dereferences the temporary created by x+4 

} 

¿Sería una sobrecarga de rvalue privada?

struct HasRef { 
    int& a; 
    HasRef(int& a):a(a){} 
    void foo(){ a=1; } 
private: 
    HasRef(int&& a); 
}; 

... HasRef r2(x+1); // doesn't compile => problem solved? 

¿Hay algún error que no haya visto?

+4

A temporal no se une a una referencia lvalue. La definición de 'r2' en su primer ejemplo no debe compilarse. – musiphil

+0

Si usa VC++, una solución es subir el nivel de advertencia y le indicará cuándo no funciona. –

+4

Sin embargo, una referencia _const_ se uniría a una temporal, por lo que la pregunta sigue siendo muy buena. He considerado este enfoque, pero sigo opinando que si una clase va a almacenar una referencia (o un puntero) al objeto al que se hace referencia, es mejor tomar un puntero en el constructor, para hacer que las preocupaciones potenciales de por vida sean una Un poco más obvio (cuando un constructor toma un puntero, generalmente me hace pensar dos veces acerca de lo que el objeto va a hacer con él). –

Respuesta

2

Eso no debería compilar. Un buen compilador de C++ (o casi cualquier compilador de C++ que haya visto) impedirá que eso suceda.

+0

gracias; parece que tengo que mirar mejor mi código y reformular el problema. – xtofl

3

Ignorando el hecho de que el código no es válido y simplemente responder a la pregunta acerca de la sobrecarga privada ...

En C++ 11 yo preferiría una función eliminado a una función privada. Es un poco más explícito que realmente no se puede llamar (ni siquiera si usted es un miembro o amigo de la clase.)

N. B. Si el constructor eliminado es HasRef(int&&)=delete no será elegido aquí:

int i; 
HasRef hr(std::forward<const int>(i)); 

Con un argumento de tipo const int&& la HasRef(const int&) constructor se utilizaría, no el HasRef(int&&) uno. En este caso, sería bien, porque i realmente es un valor-I, pero, en general, que podría no ser el caso, por lo que este podría ser uno de los momentos muy raros cuando una referencia const rvalue es útil:

HasRef(const int&&) = delete; 
0

Supongo que estás compilando en MSVS. En ese caso, apague las extensiones de idioma y debería obtener un error.

De lo contrario, ni siquiera marcando la referencia const extiende la vida útil del temporal hasta que el constructor finaliza. Después de eso, te referirás a un objeto inválido.

+0

Es más fácil decirlo que hacerlo. Deshabilitar las extensiones de idiomas con MSVC hace quejarse su propia biblioteca estándar. –

+0

@AlexandreC. No he tenido problemas hasta ahora ... ¿suerte? –

+0

Probablemente. Recuerdo haber tenido problemas con MSVC2005. –

2

Si tiene que almacenar una referencia const a alguna instancia de tipo B en su clase A, entonces seguramente desea ser asegurado, que la vida de A instancia será superado por el tiempo de vida de B ejemplo:

B b{}; 
A a1{b}; // allowed 
A a2{B{}}; // should be denied 
B const f() { return B{}; } // const result type may make sense for user-defined types 
A a3{f()}; // should also be denied! 

Para que sea posible, debe explícitamente = delete; todas las sobrecargas del constructor, que pueden aceptar valores r (tanto const && como &&). Para lograr esto, debe simplemente = delete; solo const && versión del constructor.

struct B {}; 

struct A 
{ 
    B const & b; 
    A(B const & bb) : b(bb) { ; } // accepts only `B const &` and `B &` 
    A(B const &&) = delete; // prohibits both `B &&` and `B const &&` 
}; 

Este enfoque le permite prohibir pasar al constructor todo tipo de valores.

Esto también funciona para escalares incorporados.Por ejemplo, double const f() { return 0.01; }, aunque provocar una advertencia como:

advertencia: 'const' calificador de tipo de tipo de retorno no tiene ningún efecto [-Wignored-calificadores]

todavía puede tiene efecto si simplemente = delete; única && versión del constructor:

struct A 
{ 
    double const & eps; 
    A(double const & e) : eps(e) {} // binds to `double const &`, `double &` AND ! `double const &&` 
    A(double &&) = delete; // prohibit to binding only to `double &&`, but not to `double const &&` 
}; 

double const get_eps() { return 0.01; } 

A a{0.01}; // hard error 
A a{get_eps()}; // no hard error, but it is wrong! 

para constructores no de conversión (es decir, no unario) hay un problema: es posible que tenga que proporcionar = delete; versiones -d para todos los combinatorically posibles versiones de los constructores de la siguiente manera:

struct A 
{ 
    A(B const &, C const &) {} 
    A(B const &&, C const &&) = delete; 
    // and also! 
    A(B const &, C const &&) = delete; 
    A(B const &&, C const &) = delete; 
}; 

para prohibir mixtos casos como:

B b{}; 
A a{b, C{}}; 
+0

¡Buen punto, los constructores multi arg! Probablemente tendría sentido convertir a todos los params en plantillas, y afirmar estáticamente que todos son referencias lvalue de alguna manera. Eso evitaría la explosión mixta. – xtofl

+0

@xtofl Simple 'static_assert' no es una buena elección, cuando aplica un rasgo de tipo como' std :: is_constructible' a 'A'. Necesita SFINAE-out combinaciones erróneas o utilizar * Concept Lite * si está disponible. – Orient

Cuestiones relacionadas