2010-06-30 14 views
7

Aquí hay una pregunta interesante sobre las diversas peculiaridades del lenguaje C++. Tengo un par de funciones, que se supone que llenan una serie de puntos con las esquinas de un rectángulo. Hay dos sobrecargas: una toma un Point[5], la otra toma un Point[4]. La versión de 5 puntos se refiere a un polígono cerrado, mientras que la versión de 4 puntos es cuando solo quieres las 4 esquinas, punto.C++ elenco a la matriz de un tamaño más pequeño

Obviamente, aquí hay algo de duplicación de trabajo, por lo que me gustaría poder utilizar la versión de 4 puntos para llenar los primeros 4 puntos de la versión de 5 puntos, así que no estoy duplicando ese código. (No es que sea mucho para duplicar, pero tengo reacciones alérgicas terribles cada vez que copio y pego código, y me gustaría evitar eso.)

El problema es que a C++ no parece importarle la idea de convirtiendo un T[m] a un T[n] donde n < m. static_cast parece pensar que los tipos son incompatibles por alguna razón. reinterpret_cast lo maneja bien, por supuesto, pero es un animal peligroso que, como regla general, es mejor evitar si es posible.

Así que mi pregunta es: ¿hay una forma segura de tipo de lanzar una matriz de un tamaño a una matriz de un tamaño más pequeño donde el tipo de matriz es el mismo?

[Editar] Código, sí. Debería haber mencionado que el parámetro es realmente una referencia a una matriz, no simplemente un puntero, por lo que el compilador conoce la diferencia de tipo.

void RectToPointArray(const degRect& rect, degPoint(&points)[4]) 
{ 
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon; 
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon; 
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon; 
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon; 
} 
void RectToPointArray(const degRect& rect, degPoint(&points)[5]) 
{ 
    // I would like to use a more type-safe check here if possible: 
    RectToPointArray(rect, reinterpret_cast<degPoint(&)[4]> (points)); 
    points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon; 
} 

[Edit2] El punto de pasar una matriz por referencia es por lo que podemos ser al menos vagamente seguro de que la persona que llama está pasando en una correcta "fuera parámetro".

+0

Por favor, publique el código, específicamente sus declaraciones de funciones. Las funciones que se declaran para tomar parámetros como matrices en realidad toman parámetros como punteros para que no se pueda sobrecargar por una diferencia en el tamaño de la matriz de un parámetro. –

+0

Debería haber mencionado que en realidad son matrices por referencia, por lo que el compilador tiene conocimiento del tamaño de la matriz. –

Respuesta

4

No creo que sea una buena idea hacerlo por sobrecarga. El nombre de la función no le dice a la persona que llama si va a llenar una matriz abierta o no. ¿Y qué pasa si la persona que llama solo tiene un puntero y quiere completar las coordenadas (digamos que quiere llenar varios rectángulos para formar parte de una matriz más grande en diferentes desplazamientos)?

Haría esto por dos funciones, y les dejaré tomar punteros. El tamaño no forma parte del tipo de puntero

void fillOpenRect(degRect const& rect, degPoint *p) { 
    ... 
} 

void fillClosedRect(degRect const& rect, degPoint *p) { 
    fillOpenRect(rect, p); p[4] = p[0]; 
} 

No veo qué hay de malo en esto. Su reinterpretación debería funcionar bien en la práctica (no veo qué podría salir mal, tanto la alineación como la representación serán correctas, así que la indefinición formal no se materializará en la realidad, creo), pero como dije arriba, creo que no hay una buena razón para hacer que estas funciones tomen las matrices por referencia.


Si desea hacerlo de forma genérica, se puede escribir por iteradores salida

template<typename OutputIterator> 
OutputIterator fillOpenRect(degRect const& rect, OutputIterator out) { 
    typedef typename iterator_traits<OutputIterator>::value_type value_type; 
    value_type pt[] = { 
    { rect.nw.lat, rect.nw.lon }, 
    { rect.nw.lat, rect.se.lon }, 
    { rect.se.lat, rect.se.lon }, 
    { rect.se.lat, rect.nw.lon } 
    }; 
    for(int i = 0; i < 4; i++) 
    *out++ = pt[i]; 
    return out; 
} 

template<typename OutputIterator> 
OutputIterator fillClosedRect(degRect const& rect, OutputIterator out) { 
    typedef typename iterator_traits<OutputIterator>::value_type value_type; 
    out = fillOpenRect(rect, out); 

    value_type p1 = { rect.nw.lat, rect.nw.lon }; 
    *out++ = p1; 
    return out; 
} 

entonces usted puede utilizar con vectores y matrices, con lo que la mayoría prefiere.

std::vector<degPoint> points; 
fillClosedRect(someRect, std::back_inserter(points)); 

degPoint points[5]; 
fillClosedRect(someRect, points); 

Si desea escribir código más seguro, puede utilizar la forma de vectores con las copias de elementos de inserción, y si se trabaja con código de nivel inferior, se puede utilizar un puntero como iterador de salida.

+0

Al pasar una referencia a una matriz, el compilador realizará la verificación de tipo y verificará que el tamaño de la matriz es correcto, dando un error de tiempo de compilación si se invoca a fillClosedRect por error en un rect de 4 puntos. ¿Es posible transmitir estáticamente una matriz de un tamaño a una matriz de un tamaño diferente? –

+0

@Stephen no es posible emitir estáticos tales cosas. Pero el punto del compilador que lo verifica es discutible. Solo puede pasar matrices de ese tamaño específico e incluso si el compilador verifica el tamaño, eso solo cubre las comprobaciones de tipo. No garantiza que la referencia se refiera a un objeto de matriz apropiado. Las referencias colgantes seguirán siendo posibles si la persona que llama se equivoca en alguna parte. Estoy a favor de verificar el tiempo de compilación, pero en este caso no parece tener ninguna ventaja. –

3

me gustaría utilizar std::vector o (esto es realmente malo y no debe utilizarse) en algunos casos extremos, incluso se puede utilizar matrices de civil a través de puntero como Point* y entonces no debería tener tales "casting" problemas .

1

¿Por qué no sólo tiene que pasar un puntero estándar, en lugar de uno grande, como este

void RectToPointArray(const degRect& rect, degPoint * points) ; 
+0

Falta de seguridad tipo. –

+0

Eso parece un poco extremo, – bobobobo

+0

Eso es discutible. Esta función reemplaza una cantidad de casos en los que esta conversión se realiza de forma manual y, en todos los casos, sin excepción, el conjunto de puntos se mantiene en una matriz de pila que se descarta al final del alcance, por lo que conocer el tamaño no es un problema (y esta operación en una matriz de montón simplemente no tendría ningún sentido para la aplicación). El objetivo aquí es, en general, arrojar errores de compilación en lugar de violaciones de acceso que luego deben rastrearse a través de la base de código masiva. –

0

Creo que se puede utilizar especialización de plantilla de función, como esto (ejemplo simplificado, donde fue ignorada primer argumento y nombre de la función fue reemplazado por f(), etc.):

#include <iostream> 
using namespace std; 

class X 
{ 
}; 

template<int sz, int n> 
int f(X (&x)[sz]) 
{ 
    cout<<"process "<<n<<" entries in a "<<sz<<"-dimensional array"<<endl; 
    int partial_result=f<sz,n-1>(x); 
    cout<<"process last entry..."<<endl; 

    return n; 
} 
//template specialization for sz=5 and n=4 (number of entries to process) 
template<> 
int f<5,4>(X (&x)[5]) 
{ 
    cout<<"process only the first "<<4<<" entries here..."<<endl; 

    return 4; 
} 


int main(void) 
{ 
    X u[5]; 

    int res=f<5,5>(u); 
    return 0; 
} 

por supuesto que tendría que hacerse cargo de otras (potencialmente peligrosas) casos especiales como n = {0,1,2,3} y probablemente estés mejor usando int sin firmar en lugar de Ints.

1

No creo que su encuadre/pensamiento del problema sea correcto. Generalmente, no es necesario escribir concretamente un objeto que tenga 4 vértices frente a un objeto que tenga 5.

Pero si DEBE escribirlo, puede usar struct para definir los tipos en su lugar.

struct Coord 
{ 
    float lat, long ; 
} ; 

Entonces

struct Rectangle 
{ 
    Coord points[ 4 ] ; 
} ; 

struct Pentagon 
{ 
    Coord points[ 5 ] ; 
} ; 

Entonces,

// 4 pt version 
void RectToPointArray(const degRect& rect, const Rectangle& rectangle) ; 

// 5 pt version 
void RectToPointArray(const degRect& rect, const Pentagon& pent) ; 

Creo que esta solución es un poco extremo, sin embargo, y una std::vector<Coord> que compruebe su tamaño (para ser de 4 o 5) como se esperaba con assert s, estaría bien. Hay una manera de tipo seguro de echar una serie de un tamaño a una matriz de un tamaño más pequeño donde el tipo de matriz es el mismo:

+0

Esto no es realmente una opción. Si fuera un proyecto nuevo, estaría de acuerdo con usted, pero este es un programa extremadamente antiguo, y ese nivel de refactorización simplemente no está en la hoja de ruta en este momento. Gracias por la sugerencia, sin embargo. :) –

0

Así que mi pregunta es?

No. No creo que el lenguaje le permita hacer esto en absoluto: considere el envío de int [10] a int [5]. Sin embargo, siempre puede obtener un puntero, pero no podemos 'engañar' al compilador para que piense que un tamaño fijo tiene un número diferente de dimensiones.

Si no vas a utilizar std :: vector o algún otro contenedor que pueda identificar correctamente el número de puntos dentro del tiempo de ejecución, hazlo cómodamente en una función en lugar de dos sobrecargas de funciones que se llaman en función del número de elementos, en lugar de tratar de hacer moldes de locos, considere esto, al menos, como una mejora:

void RectToPointArray(const degRect& rect, degPoint* points, unsigned int size); 

Si ya está listo para trabajar con matrices, todavía se puede definir una función genérica como esto:

template <class T, size_t N> 
std::size_t array_size(const T(&/*array*/)[N]) 
{ 
    return N; 
} 

... y usar eso cuando llame ing RectToPointArray para pasar el argumento de 'tamaño'. Luego tiene un tamaño que puede determinar en tiempo de ejecución y es bastante fácil trabajar con el tamaño - 1, o más apropiado para este caso, simplemente ponga una declaración simple if para verificar si hay 5 elementos o 4.

Más adelante, si cambia de opinión y utiliza std :: vector, Boost.Array, etc., puede seguir utilizando esta misma función anterior sin modificarla. Solo requiere que los datos sean contiguos y mutables. Puede hacerse elegante con esto y aplicar soluciones muy genéricas que, por ejemplo, solo requieren iteradores directos. Sin embargo, no creo que este problema sea lo suficientemente complicado como para garantizar tal solución: sería como usar un cañón para matar una mosca; matamoscas está bien.

Si realmente fijan en la solución es así, entonces es bastante fácil de hacer esto:

template <size_t N> 
void RectToPointArray(const degRect& rect, degPoint(&points)[N]) 
{ 
    assert(N >= 4 && "points requires at least 4 elements!"); 
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon; 
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon; 
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon; 
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon; 

    if (N >= 5) 
     points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon; 
} 

Sí, hay una comprobación en tiempo de ejecución es innecesario, sino tratando de hacerlo en tiempo de compilación es probablemente análoga sacar cosas de su guantera en un intento de aumentar la eficiencia del combustible de su automóvil. Como N es una expresión constante en tiempo de compilación, es probable que el compilador reconozca que la condición siempre es falsa cuando N < 5 y acaba de eliminar toda esa sección del código.

Cuestiones relacionadas