Asegúrese en tiempo de compilación de que se llama a un método exactamente en un lugar

15

Tengo curiosidad por saber si es posible garantizar en tiempo de compilación que un método se llame exactamente en un lugar.

Tenga en cuenta que está bien si la función se llama más de una vez (por ejemplo, en un bucle), pero no se debe llamar en dos bucles separados.

Esto se puede dividir en dos partes, también estoy interesado en las soluciones que cubren cualquiera de las partes:
(a) asegurar que se llame a un método en al menos un lugar
(b) asegurar que se llame a un método en un lugar como máximo

Tengo control total sobre la estructura del código, y son bienvenidos diferentes modismos que logran la misma idea.

// class.h

class MyClass {
  public:
    void my_method();
}

Lo siguiente no debe compilarse (nunca llamado)

#include "class.h"

int main() {
  MyClass my_class;
}

Lo siguiente no debe compilarse (llamado en más de un lugar)

#include "class.h"

int main() {
  MyClass my_class;
  my_class.my_method();
  while(true) {
    my_class.my_method();
  }
}

Lo siguiente debe compilarse (llamado exactamente en un lugar):

#include "class.h"

int main() {
  MyClass my_class;
  while(true) {
    my_class.my_method();
  }
}
yoyoy
fuente
2
No lo conviertas en un método. Ponga el código en línea en ese único lugar.
user207421
2
Creo que también podría hacer esto con una lambda (podría ser una lambda vacía) porque el tipo de cierre es único para cada lambda. Nuevamente, esto sería un error de tiempo de ejecución, pero eso no es lo que solicitó. Si proporciona más detalles sobre el problema que está tratando de resolver, entonces podríamos encontrar una solución.
Indiana Kernick
2
Puede usar la __COUNTER__macro no estándar para hacer esto. Algo así como static_assert(__COUNTER__ == 0); my_class.my_method();. Sin embargo, el contador se reinicia en cada unidad de traducción, por lo que solo puede verificar que la función se llame una vez por unidad de traducción.
Indiana Kernick
44
¿Por qué quieres hacer eso? Parte del objetivo de una función es que se puede invocar desde múltiples lugares.
Chipster
44
Deberías explicar por qué quieres hacer esto. Quizás la solución que estás pidiendo no sea la mejor para lograr tus objetivos reales.
diez

Respuestas:

6

Enfoque de baja tecnología:

Dado que tiene control sobre la estructura del código (que incluye el sistema de compilación, supongo), aquí hay una solución de baja tecnología:

  • hacer que el nombre de la función sea lo suficientemente único
  • grep para el nombre de la función en su código. Lo espera dos veces (suponiendo que su declaración y definición estén colocadas):
    • Una vez en el encabezado
    • Una vez en el sitio de una sola llamada

Alternativamente:

Si realmente quieres resolverlo con C ++, entonces puedes intentar

  • Use un contador de tiempo de compilación para calcular la cantidad de usos dentro de las unidades de compilación
  • Asegúrese de que la función violaría ODR si el encabezado se incluye en varias unidades de compilación.

Sin embargo, los contadores de tiempo de compilación son magia negra (dice I, y realmente me gusta TMP), y forzar violaciones de ODR para este propósito parece un vudú similar (al menos requeriría un caso de prueba que no se vincule).

Pero en serio:

No hagas esto. Hagas lo que hagas, puede pervertirse casi sin esfuerzo mediante una función de envoltura:

auto call_my_method(MyClass& o)
{
   return o.my_method();
}

MyClass::my_method()se llama solo en el contenedor. Todos los demás simplemente llaman al reiniciador, que probablemente incluso está compilado por el compilador.

Como otros sugirieron: podría ser mucho más útil si explicas lo que estás tratando de hacer.

Rumburak
fuente
1

Aquí hay una idea aproximada que puede funcionar (demasiado tiempo para un comentario, pero incompleta para una buena respuesta SO).

Puede lograr esto contando / verificando las instancias de plantillas.
Las plantillas se instancian solo con el uso .

De manera similar, los cuerpos de método / función de plantilla no se analizan ni compilan ni vinculan (más allá de garantizar una sintaxis válida) si nunca se llaman. Esto significa que no se realizan instancias dentro de sus cuerpos).

Es posible que pueda crear una plantilla que mantenga un recuento global de instancias y una afirmación estática sobre eso (o algún otro mecanismo TMP para verificar instancias pasadas).

Adi Shavit
fuente
El recuento de instancias "globales" sería local para la unidad de compilación actual.
atomymbol
1

Hay una solución parcial a esta pregunta utilizando el preprocesador C y el ensamblaje en línea GNU:

Archivo de encabezado a.h:

struct A {
    // Do not call this method directly, use the macro below to call it
    int _method_vUcaJB5NKSD3upQ(int i, int j);
};

// Use inline assembly to ensure that this macro is used at most once
#define method_vUcaJB5NKSD3upQ(args...) \
    _method_vUcaJB5NKSD3upQ(args); \
    asm (".global once_vUcaJB5NKSD3upQ; once_vUcaJB5NKSD3upQ:");

Archivo de implementación a.cc:

#include <iostream>
#include "a.h"

int A::_method_vUcaJB5NKSD3upQ(int i, int j) { return i+j+5; }

// Ensure that the macro is used at least once
extern "C" const char once_vUcaJB5NKSD3upQ;
static const char get_vUcaJB5NKSD3upQ = once_vUcaJB5NKSD3upQ;

int main() {
    A a;
    for(int i=0; i<7; i++) {
        // Use a separate statement to call the method
        // (terminated by a semicolon, it cannot be a sub-expression)
        auto x = a.method_vUcaJB5NKSD3upQ(2, 3);
        std::cout << x << std::endl;
    }
    return 0;
}

Esta solución es parcial en el sentido de que no impide que el programa llame al método que comienza con el guión bajo directamente sin usar la macro de contenedor.

símbolo de los átomos
fuente
0

Use un contador constexpr. Hay una implementación en otra pregunta

SD57
fuente
1
Parece que este método está mal formado.
Chipster
El problema open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2118 al que se hace referencia en una respuesta a esa pregunta indica que es un error del estándar y debe estar mal formado.
SD57
Entonces, ¿no está mal formado, al menos no todavía?
Chipster
Si aún no está mal formado, debe ser utilizado por la mayor cantidad de personas posible lo más rápido posible, para que tengan que apoyar este caso de uso.
user1685095