¿Se evalúa el argumento `this` antes o después de otros argumentos de función miembro?

6 minutos de lectura

avatar de usuario de mentalmushroom
hongo mental

En el siguiente código, una función miembro set() es llamado en un model, que es un puntero nulo. Esto sería un comportamiento indefinido. Sin embargo, el parámetro de la función miembro es el resultado de otra llamada de función que verifica si el model es un puntero nulo y se lanza en ese caso. ¿Está garantizado que estimate() siempre será llamado antes de la model se accede o sigue siendo un comportamiento indefinido (UB)?

#include <iostream>
#include <memory>
#include <vector>


struct Model
{
    void set(int x)
    {
        v.resize(x);
    }

    std::vector<double> v;
};


int estimate(std::shared_ptr<Model> m)
{
    return m ? 3 : throw std::runtime_error("Model is not set");
}

int main()
{
    try
    {
        std::shared_ptr<Model> model; // null pointer here
        model->set(estimate(model));
    }
    catch (const std::runtime_error& e)
    {
        std::cout << e.what();
    }

    return 0;
}

  • Bueno el estimate se garantiza que la llamada se llamará primero. Lo que significa que la excepción se lanzará antes de que ocurra la desreferencia real del puntero nulo. ¿El código sigue siendo UB incluso cuando el código que causa la UB no se ejecuta? Esa es una pregunta interesante… 🙂

    – Un tipo programador

    9 de mayo a las 8:33

  • ¿Su pregunta es puramente por curiosidad o se basa en un código real? No organizaría mi código basándome en el orden de evaluación…

    – Mickaël C. Guimarães

    9 de mayo a las 8:34

  • Creo que es perfectamente razonable escribir código basado en el hecho de que los argumentos de la función se evaluarán antes que la función misma.

    – John

    9 de mayo a las 8:36


  • @463035818_is_not_a_number: de ahí mi comentario.

    – Yves Daoust

    9 de mayo a las 8:41


  • FWIW, desinfectante de direcciones dice -> se evalúa de todos modos godbolt.org/z/n13P18M3G. Además, el uso de punteros en bruto (que básicamente deberían ser el mismo problema aquí) da el mismo diagnóstico godbolt.org/z/TPT7Y89cG

    – cién

    9 de mayo a las 9:25


Avatar de usuario de Jason
jason

Esto sigue siendo un comportamiento indefinido (UB) según expr.compuesto:

El postfijo-expresión se secuencia antes de cada expresión en el lista de expresiones y cualquier argumento predeterminado. La inicialización de un parámetro, incluidos todos los cálculos de valores asociados y los efectos secundarios, tiene una secuencia indeterminada con respecto a la de cualquier otro parámetro.

(énfasis mío)

Esto significa que la expresión posfija model->set se secuencia antes de la expresión estimate(model) en la lista de expresiones. Y desde model es puntero nulo, la condición previa de std::shared_ptr::operator-> es violado y por lo tanto esto conduce a UB.


  • Entonces, si lo hago bien desde model-> es equivalente a (*model). esto significa que model todavía está desreferenciado y, por lo tanto, este sigue siendo un comportamiento indefinido.

    – Vale

    9 de mayo a las 9:27


  • No está muy claro cómo UB se deduce de esta declaración. Se evalúa la expresión de sufijo antes de la flecha, lo que da como resultado un puntero nulo. Junto con la expresión de identificación, determina el resultado de la expresión completa. Ok, pero ¿elimina la referencia al puntero nulo para determinarlo?

    – hongo mental

    9 de mayo a las 10:25

  • @mentalmushroom si set es virtual el puntero tiene que ser desreferenciado para enviar la llamada en función del tipo dinámico del objeto. Este caso no está señalado en la norma (a pesar de que un caso novirtual función podría despacharse basándose únicamente en el tipo estático de model), por lo tanto, el caso general es que el puntero tiene ser desreferenciable.

    – Quintín

    9 de mayo a las 13:45


  • @mentalmushroom Tu uso de std::shared_ptr hace esto bastante claro, porque el std::shared_ptr::operator-> la sobrecarga tiene una condición previa por especificación de biblioteca de que el puntero no es nulo. la llamada a eso Por lo tanto, la función ya tiene un comportamiento indefinido, independientemente de la evaluación de ->set sobre el resultado Si lo mismo sería cierto con un puntero nulo sin procesar es una pregunta diferente.

    – usuario17732522

    9 de mayo a las 14:46


  • @ user17732522 He agregado una segunda parte en mi respuesta que usa expr.ref. Básicamente a mi entender de [expr.ref]: “la expresión posfijo model antes de que se evalúe la flecha y el resultado de esa evaluación (que es nullptr) junto con la expresión-id operator-> se utiliza para determinar el resultado de toda la expresión del sufijo”. Esto conduce a UB ya que se viola la condición previa. ¿Es la conclusión y el análisis usando [expr.ref] ¿correcto?

    – Jasón

    9 de mayo a las 15:05


A mi entender, este es un comportamiento indefinido. al menos desde C++ 17:

  1. En una expresión de llamada a función, la expresión que nombra la función se secuencia antes de cada expresión de argumento y cada argumento predeterminado.

Como interpreto esto, en realidad garantiza que model->set se evalúa antes de cualquier argumento y, por lo tanto, invoca un comportamiento indefinido. No importa si o no model es un puntero crudo.

  • Pero model->set nombra una función miembro y no una función, por lo que esto no parece aplicarse. El título de la pregunta también sugiere explícitamente que OP está interesado en el caso de la función miembro y ya conoce el caso de la función no miembro.

    – Vale

    9 de mayo a las 9:32


  • @Val Entonces, ¿una función miembro no es una función? ¿En qué basas esa afirmación?

    – nielsen

    9 de mayo a las 9:36

  • La llamada de función y la llamada de función miembro son dos cosas diferentes.

    – Vale

    9 de mayo a las 9:39

  • @Val Esto es cppreference, no el estándar. Este texto está escrito para facilitar la lectura, no para la máxima precisión. El regla real dice que el postfijo-expresión se secuencia antes de las inicializaciones del argumento. La regla citada de cppreference se aplica aquí.

    – j6t

    9 de mayo a las 10:18


  • @Val Incluso en el estándar, creo que la palabra “función” cubre las funciones de los miembros, así como las funciones de los no miembros, y se hace una distinción explícita cuando corresponde. En términos generales, una “llamada a una función miembro” es un caso especial de una “llamada a una función”.

    – nielsen

    9 de mayo a las 11:01

[expr.call]/7:

El postfijo-expresión se secuencia antes de cada expresión en el lista de expresiones y cualquier argumento predeterminado.

En este caso, esto significa que model->set se evalúa antes estimate(model).

Desde model es un shared_ptr<Model>, model->set usos shared_ptrestá sobrecargado operator->que tiene la siguiente precondición ([util.smartptr.shared.obs]/5):

Condiciones previas: get() != nullptr.

La violación de esta condición previa da como resultado un comportamiento indefinido ([structure.specifications]/3.3).

¿Ha sido útil esta solución?