1. ホーム
  2. c++

[解決済み] 便利なVector3fクラス

2022-02-27 18:02:03

質問

を持つ必要がある場合があります。 Vector3f クラスがあり、そのクラスは x , yz のメンバーとしてインデックス化することができます。 float[3] の配列を同時に作成することができます(これについては、すでにこのSOでいくつかの質問があります)。

のようなものです。

struct Vector3f {
    float data[3];
    float &x = data[0];
    float &y = data[1];
    float &z = data[2];
};

これを使えば、こう書ける。

Vector3f v;
v.x = 2.0f;
v.y = 3.0f;
v.z = 4.0f;
glVertex3fv(v.data);

しかし、この実装では、参照が struct (これは非常に残念なことです。この特別なケースで参照を削除できない理由は見当たりません。おそらく、コンパイラ側の最適化ミスでしょう)。

しかし [[no_unique_address]] こんなアイデアもありました。

#include <new>

template <int INDEX>
class Vector3fProperty {
    public:
        operator float() const {
            return propertyValue();
        }
        float &operator=(float value) {
            float &v = propertyValue();
            v = value;
            return v;
        }
    private:
        float &propertyValue() {
            return std::launder(reinterpret_cast<float*>(this))[INDEX];
        }
        float propertyValue() const {
            return std::launder(reinterpret_cast<const float*>(this))[INDEX];
        }
};

struct Vector3f {
    [[no_unique_address]]
    Vector3fProperty<0> x;
    [[no_unique_address]]
    Vector3fProperty<1> y;
    [[no_unique_address]]
    Vector3fProperty<2> z;

    float data[3];
};

static_assert(sizeof(Vector3f)==12);

つまり、基本的には、プロパティに struct へのアクセスを処理します。 x , yz . これらのプロパティは、空であるためスペースを取らないようにする必要があり、その属性は [[no_unique_address]]

この方法についてどう思われますか?UBがあるのでしょうか?


なお、この質問は、これらすべてが可能なクラスについてのものです。

Vector3f v;
v.x = 1;
float tmp = v.x;
float *c = v.<something>; // there, c points to a float[3] array

解決方法は?

<ブロッククオート

しかし、この実装は、参照が構造体のスペースを取るので、まずいです(これは非常に残念なことです。この特殊なケースで参照を削除できない理由は見当たりませんが、もしかしたらコンパイラ側の最適化ミスかもしれません)。

これは複雑な問題のようですね。標準レイアウトのクラスは、互いに互換性がなければなりません。そのため、コンパイラは、どのように定義されているかにかかわらず、メンバーを排除することはできません。非標準レイアウトの場合は?それはわからない。詳しくは、これを読んでください。 C++標準は、未使用のプライベートフィールドがsizeofに影響を与えることを保証していますか?

私の経験では、コンパイラはクラスのメンバを、たとえ "unused"であっても削除しません(たとえば、形式的には sizeof はそれらを使用しています)。

<ブロッククオート

UBは搭載されていますか?

これはUBだと思います。まず第一に [[no_unique_address]] は、そのメンバーが は必要ありません。 は一意なアドレスを持つのであって はしてはならない は一意なアドレスを持っています。次に、あなたの data メンバが始まります。ここでも、コンパイラは、以前の [[no_unique_address]] クラスのメンバーです。つまり、不正確なメモリにアクセスする可能性があるということです。

もう一つの問題は、"inner" クラスから "outer" メモリにアクセスしたいことです。このようなことは、C++ではUBである。

<ブロッククオート

この方法についてどう思われますか?

それが正しいとして(正しくないが)、私はまだそれが好きではありません。あなたはゲッター/セッターが欲しいのでしょうが、C++はこの機能をサポートしていません。だから、そのような奇妙で複雑な構造をする代わりに(このコードを他の人がメンテナンスすることを想像してください)、単純に次のようにするのはどうでしょう。

struct Vector3f {
    float data[3];
    float x() {
        return data[0];
    }
    void x(float value) {
        data[0] = value;
    }
    ...
};

あなたはこのコードが醜いと言っています。そうかもしれません。しかし、シンプルで、読みやすく、メンテナンスしやすいコードです。UBもないし、ユニオンを使ったハックの可能性にも依存しないし、美の追求を除けば、まさにあなたが望むことをやってくれるんです :)