4
votes
\$\begingroup\$

If you were given the following:

#include <cassert>

struct vec {
    float x,y,z;
    vec(float x, float y, float z) : x(x), y(y), z(z) {}
};

int main() {
    float verts[] = {1,2,3,4,5,6,7,8,9}; // length guaranteed to be a multiple of 3
    assert(sizeof(vec) == sizeof(float) * 3);

    // Option 1
    for (int i = 0; i < 3; ++i) {
        auto a = reinterpret_cast<vec*>(verts)[i];
        // ...
    }

    // Option 2
    for (int i = 0; i < 9; i += 3) {
        vec a(verts[i], verts[i+1], verts[i+2]); // -O3 averts the copy, so d/w
        // ...
    }

    return 0;
}

Which is considered better practice?

The advantage to the latter option is of course far more explicit, and carries all the advantages/disadvantages that contains; but may not be portable depending on the assertions you can make.

\$\endgroup\$
3
votes
\$\begingroup\$

I would go with a 3rd option that is closest to option 2. But instead of taking the three parameters separately in the constructor, take an array of floats.

struct vec {
    float x,y,z;
    vec(float fv[]) : x(fv[0]), y(fv[1]), z(fv[2]) {}
};

int main() {
    float verts[] = {1,2,3,4,5,6,7,8,9};

    for (int i = 0; i < 9; i += 3) {
        const vec a(&verts[i]);
        // ...
    }

    return 0;
}
\$\endgroup\$
  • \$\begingroup\$ A great idea, averts extraneous syntax, clear intent, and all the behaviour is defined. Also allows for the original data to be stored as floats. \$\endgroup\$ – deceleratedcaviar Aug 25 '11 at 23:09
1
vote
\$\begingroup\$

I strongly prefer option 3.

Options 1 and 2 remind me most of K&R C where a pointer is a pointer is a pointer. If one of the types is changed, the compiler may not notice and that can lead to bugs that may be hard to locate.

Option 3 is the style used in a strongly typed language. If one of the types is changed, the compiler is more likely to notice and complain at the point where code needs to be fixed.

\$\endgroup\$
1
vote
\$\begingroup\$

Why not do this:

struct vec
{
    float x,y,z;
};


int main()
{
    vec verts[] = {{1,2,3}, {4,5,6}, {7,8,9}};

It is definitely more clear than any of the proposed solutions.
Is type safe (a key point) and avoids the copy you want (as they are created in place).

// Option 4
for (int i = 0; i < 3; ++i)
{
    const vec& a    = verts[i];

}

When writing C++ code you should definitely not use the C cast operator. C++ has four of its own that you can use:

const_cast           // If you need this you are either very clever or your design is wrong
reinterpret_cast     // This is an indication that you are doing something non portable.
static_cast          // This is a good cast (but any explicit cast is bad)
dynamic_cast         // This casts up and down the class hierarchy (usually just down)

                     // Note: dynamic_cast is a runtime operation.
                     //       all others are compile time casts.
                     //       static_cast/dynamic_cast will actually generate compile
                     //       time errors if you do something wrong very wrong. dynamic_cast
                     //       can also throw an exception (or return NULL).

In your case when I convert your C-casts into C++

   // option 1
   const vec& a = reinterpret_cast<vec&>(verts[i]);

   // Option 2
   const vec& a = reinterpret_cast<vec*>(verts)[i];

Now if you present your code to a C++ developer the first thing they will do is ask: why do you need reinterpret_cast? That's a bad idea.

\$\endgroup\$
  • \$\begingroup\$ A valid point, but some libraries in my code require the data as a pointer to floats. The POD way triumphs unless I use reinterpret cast for these situations (agreed, yuk). \$\endgroup\$ – deceleratedcaviar Aug 25 '11 at 16:49
0
votes
\$\begingroup\$

I'd say, use the last one until this prooves to by a performance problem (which I highly doubt). Also, be aware that the first two solution would modify the array, should the following code modify a.

\$\endgroup\$
  • \$\begingroup\$ The compile generally brings this all down to the same code (or close enough, perhaps 2 different ops), I'm just asking from a programmers point of view; and perhaps from a standards point of view as well. \$\endgroup\$ – deceleratedcaviar Aug 25 '11 at 15:50

Not the answer you're looking for? Browse other questions tagged or ask your own question.