starr - Alias de miembros de C++
the beatles wikipedia (12)
Estoy bastante seguro de que esto es posible, porque estoy bastante seguro de que lo he visto hecho. Creo que es increíble, pero con mucho gusto aceptaré respuestas como "esta es una idea terrible porque ____".
Digamos que tenemos una estructura básica.
struct vertex
{
float x, y, z;
};
Ahora, quiero implementar alias en estas variables.
vertex pos;
vertex col;
vertex arr;
pos.x = 0.0f; pos.y = 0.5f; pos.z = 1.0f;
col.r = 0.0f; col.g = 0.5f; col.b = 1.0f;
arr[0] = 0.0f; arr[1] = 0.5f; arr[2] = 1.0f;
Idealmente, la tercera sintaxis sería indistinguible de una matriz. Es decir, si envié arr
como parámetro de referencia a una función que espera una serie de flotantes en los que almacenará datos (por ejemplo, muchas de las funciones de OpenGL glGet
), funcionaría bien.
¿Qué piensas? ¿Posible? Posible pero estúpido?
¿Referencias?
template<typename T>
struct vertex {
vertex() :
r(data[0]), g(data[1]), b(data[2]),
x(data[0]), y(data[1]), z(data[2])
{
}
T *operator *() {
return data;
}
const T *operator *() const {
return data;
}
T data[3];
T &r, &g, &b;
T &x, &y, &z;
};
Las estructuras anidadas sin nombre en una unión no son estándar C ++. Esto, sin embargo, debería funcionar:
struct Vertex
{
private:
typedef float Vertex::* const vert[3];
static const vert v;
public:
typedef size_t size_type;
float x, y, z;
const float& operator[](size_type i) const {
return this->*v[i];
}
float& operator[](size_type i) {
return this->*v[i];
}
};
const Vertex::vert Vertex::v = {&Vertex::x, &Vertex::y, &Vertex::z};
EDITAR: Un poco más de información. La estructura usa una matriz de 3 miembros de puntero a datos para acceder a los datos en los operadores [] sobrecargados.
La línea "typedef float Vertex :: * const vert" significa que vert es un puntero a un miembro flotante de la estructura Vertex. El [3] significa que es una matriz de 3 de estos. En el operador sobrecargado [], este conjunto se indexa y el puntero a miembro de datos se desreferencia y se devuelve el valor.
Además, este método debería funcionar independientemente de los problemas de embalaje: el compilador puede rellenar la estructura de Vertex de la manera que quiera y funcionará igual de bien. Una unión anónima tendrá problemas si las carrozas están empacadas de manera diferente.
Lo que haría sería crear accesadores:
struct Vertex {
float& r() { return values[0]; }
float& g() { return values[1]; }
float& b() { return values[2]; }
float& x() { return values[0]; }
float& y() { return values[1]; }
float& z() { return values[2]; }
float operator [] (unsigned i) const { return this->values_[i]; }
float& operator [] (unsigned i) { return this->values_[i]; }
operator float*() const { return this->values_; }
private:
float[3] values_;
}
Mala idea, en mi opinión, al menos para el ejemplo dado: la desventaja es que, para casi cualquier solución a esto, es probable que pueda asignar libremente instancias "rgb" a / desde instancias "xyz", que es probable que rara vez sea sensato o correcto. es decir, te arriesgas a renunciar a algún tipo de seguridad útil.
Personalmente, para el ejemplo que das, subclases los tipos rgb y xyz de una base boost::array<float,3>
o similar. Por lo tanto, ambos heredan el operador [], se pueden pasar a funciones que esperan matrices y se pasan con más seguridad de tipo a cosas que esperan colores / coordenadas. A menudo quieres tratar un xyz o un rgb como una matriz, pero es raro que quieras tratar un xyz como un rgb o viceversa. (rgb IS-A array: OK. xyz IS-A array: OK. rgb IS-A xyz ???? ¡No lo creo!)
Por supuesto, eso significa que el acceso a x, y, z & r, g, b debe ser realizado por el usuario (reenviando al operator[](...)
apropiado operator[](...)
) en lugar de dirigirse al miembro. (Necesitarías las propiedades de C # para eso).
No estoy seguro si entendí la pregunta correctamente. Pero parece que necesita sobrecargar el operador [] para proporcionar un acceso tipo array a su estructura / clase. Vea el ejemplo mencionado aquí: Sobrecarga del operador
Puede intentar agregar referencias a variables, como esta:
struct test {
float x, y, z;
float &r, &g, &b;
test() : r(x), g(y), b(z) {}
};
Pero su estructura se hace más grande (de 12 bytes a 40 bytes).
Para usar [] en él, use la sobrecarga del operador [], como se mencionó anteriormente.
Puede obtener esto con una unión como otros lo han mencionado. Puede que no sea una buena idea sobrecargar el color y la posición en la misma estructura (por ejemplo, agregar dos colores generalmente significa que quieres saturar a 1.0, mientras que agregar vectores ocurre linealmente), pero superponiendo un flotador [] encima de ellos como eso está perfectamente bien y es un medio bien aceptado de intercambiar datos con GL / DirectX / etc.
Sin embargo, le recomiendo que evite referirse al mismo miembro con diferentes alias en el mismo alcance de la función, porque esto lo llevará a un desagradable puesto de hardware llamado load-hit-store. En particular, evite esto si puede:
vector foo;
foo.x = 1.0f;
return foo[0] + foo[1];
Supongo que puedes hacer algo de magia macro para obtener lo que quieres. Pero eso se verá feo. ¿Por qué quieres usar la misma estructura, vértice para 3 tipos diferentes? ¿Por qué no puedes definir clase por color? También tenga en cuenta que el vértice y el color no son iguales. Si cambia algo a vértice, eso también afectará el color, si tiene la misma clase para ambos.
Tengo una plantilla y dos clases de vectores debajo, una loca, una cuerda. La plantilla implementa un conjunto simple de valores de compilación en tiempo de compilación. Está diseñado para la creación de subclases y utiliza una variable de matriz protegida para evitar tener que pasar por aros para acceder a la matriz. (A algunas personas podría no gustarles ese diseño. Digo, si sus subclases llaman a sus operadores sobrecargados, el acoplamiento podría ser una buena idea).
La clase crazy le permite tener variables miembro llamadas x, y, z y actúa como una matriz para llamadas a glGetFloatV. El que tiene funciones accessor x (), y (), z () y todavía funciona con glGetFloatV. Puede utilizar cualquier clase como base para otros objetos vectoriales que pueda pasar a la biblioteca OpenGL. Aunque las clases a continuación son específicas de los puntos, obviamente puede hacer una búsqueda / reemplazo para convertirlas en clases de color rgb.
La clase loca es una locura porque el costo del azúcar sintáctico vec.x en lugar de vec.x () es de 3 variables de referencia. Eso podría ocupar mucho espacio en una aplicación grande. Usa la versión más simple de Creen.
template <typename T, int N>
class FixedVector {
protected:
T arr[N];
public:
FixedVector();
FixedVector(const T* a) {
for (int i = 0; i < N; ++i) {
arr[i] = a[i];
}
}
FixedVector(const T& other) {
for (int i = 0; i < N; ++i) {
arr[i] = other.arr[i];
}
}
FixedVector& operator=(const T& other) {
for (int i = 0; i < N; ++i) {
arr[i] = other.arr[i];
}
return *this;
}
T* operator&() { return arr; }
const T* operator&() const { return arr; }
T& operator[](int ofs) {
assert(ofs >= 0 && ofs < N);
return arr[ofs];
}
const T& operator[](int ofs) const {
assert(ofs >= 0 && ofs < N);
return arr[ofs];
}
};
class CrazyPoint : public FixedVector<float, 3> {
public:
float &x, &y, &z;
CrazyPoint()
: x(arr[0]), y(arr[1]), z(arr[2])
{ arr[0] = arr[1] = arr[2] = 0.0; }
CrazyPoint(const float* a)
: x(arr[0]), y(arr[1]), z(arr[2])
{
arr[0] = a[0];
arr[1] = a[1];
arr[2] = a[2];
}
CrazyPoint(float a, float b, float c)
: x(a), y(b), z(c)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
}
};
class SanePoint : public FixedVector<float, 3> {
public:
float& x() { return arr[0]; }
float& y() { return arr[1]; }
float& z() { return arr[2]; }
SanePoint() { arr[0] = arr[1] = arr[2] = 0.0; }
SanePoint(float a, float b, float c)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
}
};
// usage
SanePoint normal;
glGetFloatV(GL_CURRENT_NORMAL, &normal);
Solo una advertencia sobre el uso de miembros de referencia que apuntan a valores de miembros. Debe definir un constructor de copia (y posiblemente también un operador de asignación), si alguna vez copia ese objeto (como transferirlo por valor). El constructor de copia predeterminado le dejará una copia cuyos miembros de referencia apuntan a los miembros de valor del objeto original, no a los del objeto nuevo. Esto ciertamente no es algo que quieras.
Teniendo en cuenta que también terminas con objetos más grandes, como ya se señaló, creo que usar métodos de acceso es preferible a los miembros de referencia.
Usa una unión?
union vertex
{
struct { float x, y, z; };
struct { float r, g, b; };
float arr[3];
};
No lo recomendaría, provocará confusión.
Agregado :
Como señala Adrian en su respuesta, esta unión con miembros de estructuras anónimas no es compatible con ISO C ++. Funciona en GNU G ++ (con quejas sobre no ser compatible cuando -Wall -ansi -pedantic
'' -Wall -ansi -pedantic
''). Es una reminiscencia de los días C previos al estándar (pre y K & R 1st Edn), cuando los nombres de los elementos de estructura tenían que ser únicos en todas las estructuras, y podía usar notaciones contratadas para llegar a un desplazamiento dentro de la estructura, y podía use nombres de miembros de otros tipos de estructuras, una forma de anarquía. Para cuando comencé a usar C (hace mucho tiempo, pero después de K & R1), eso ya era uso histórico.
La notación mostrada con los miembros de la unión anónima (para las dos estructuras) es compatible con C11 (ISO / IEC 9899: 2011), pero no con las versiones anteriores del estándar C. La sección 9.5 de ISO / IEC 14882: 2011 (C ++ 11) establece uniones anónimas, pero GNU g++
(4.9.1) no acepta el código que se muestra con -pedantic
, que identifica " warning: ISO C++ prohibits anonymous structs [-Wpedantic]
".
Dado que la idea generará confusión, no estoy particularmente preocupado de que no sea estándar; No usaría el mecanismo para esta tarea (y sería receloso de usar estructuras anónimas en una unión, incluso si fuera beneficioso).
Una preocupación fue planteada:
Los tres (xyz, rgb y la matriz) no necesariamente se alinean.
Es una unión con tres elementos; los tres elementos comienzan en la misma dirección. Las dos primeras son estructuras que contienen 3 valores de flotación. No hay herencia y no hay funciones virtuales para dar diferentes diseños, etc. Las estructuras se diseñarán con los tres elementos contiguos (en la práctica, incluso si el estándar permite el relleno). El conjunto también comienza en la misma dirección, y sujeto a ''no relleno'' en las estructuras, los elementos se superponen a las dos estructuras. Realmente no veo que haya un problema.
La siguiente estructura tendrá el comportamiento solicitado:
struct vertex
{
private:
float data[3];
public:
float &x, &y, &z;
float &r, &g, &b;
vertex() : x(data[0]), y(data[1]), z(data[2]), r(data[0]), g(data[1]), b(data[2]) {
}
float& operator [](int i) {
return data[i];
}
};