【问题标题】:"Retroactive Union" - can it be done?“追溯联盟”——能做到吗?
【发布时间】:2012-09-26 01:07:44
【问题描述】:

我有两个类:一个模板类和一个继承自它的常规类:

template <int N> class Vector
{
    float data[N];
    //etc. (math, mostly)
};

class Vector3 : public Vector<3>
{
    //Vector3-specific stuff, like the cross product
};

现在,我想在子类中有 x/y/z 成员变量(完整成员,而不仅仅是 getter - 我也希望能够设置它们)。但是为了确保所有(继承的)数学都有效,x 必须引用与data[0]ydata[1] 等相同的内存。本质上,我想要一个联合,但我可以'不要在基类中声明一个,因为那时我不知道向量中的浮点数。

那么 - 这可以做到吗?是否有某种预处理器/类型定义/模板魔术可以实现我正在寻找的东西?

PS:如果有帮助,我正在使用带有 -std=c++0x 的 g++ 4.6.0。

编辑:虽然引用会给出我正在寻找的语法,但理想的解决方案不会使类变得更大(而且引用会 - 很多!Vector&lt;3&gt; 是 12 个字节. 带有引用的Vector3 是40!)。

【问题讨论】:

  • 你为什么想要这个,而不是仅仅拥有引用适当数据值的 getter 和 setter?
  • 为什么 getter 不能解决你的问题?
  • 为什么不简单地使用 getter 和 setter?与任何其他可能的解决方案相比,它的魔力要小得多。例如float x() const; float x(float newx);
  • @Dave - 因为函数式的 getter 和 setter 令人讨厌。 Kristóf 的 jQuery 风格设置相当优雅,但是……它只是一个变量,我想使用等号,该死! (我最近做了很多 Ruby 编程——我想我被宠坏了。)
  • 如果要使用等号,请使用引用 getter。 float&amp; x()const float&amp; x() const。然后你可以说v.x() = 1。你仍然需要额外的括号,但它更接近。

标签: c++ templates unions


【解决方案1】:

怎么样:

class Vector3 : public Vector<3>
{
public:
  // initialize the references...
  Vector3() : x(data[0]), y(data[1]), z(data[2]){}
private:
  float& x;
  float& y;
  float& z;
};

当然,如果你想让他们占据同一个空间,那就另当别论了……

使用一点模板魔法,您可以执行以下操作...

#include <iostream>

template <int N, typename UnionType = void*> struct Vector
{
    union
    {
      float data[N];
      UnionType field;
    };

    void set(int i, float f)
    {
      data[i] = f;
    }

    // in here, now work with data
    void print()
    {
      for(int i = 0; i < N; ++i)
        std::cout << i << ":" << data[i] << std::endl;
    }
};

// Define a structure of three floats
struct Float3
{
  float x;
  float y;
  float z;
};

struct Vector3 : public Vector<3, Float3>
{
};

int main(void)
{
  Vector<2> v1;
  v1.set(0, 0.1);
  v1.set(1, 0.2);
  v1.print();

  Vector3 v2;
  v2.field.x = 0.2;
  v2.field.y = 0.3;
  v2.field.z = 0.4;
  v2.print();

}

编辑:阅读评论后,我意识到我之前发布的内容并没有什么不同,因此对之前的迭代稍作调整以提供对该字段的直接访问(这就是我猜你所追求的) - 我猜这与下面 Rob 的解决方案之间的区别在于,您不需要所有的专业知识来一次又一次地实现所有逻辑......

【讨论】:

  • 当然,这个解决方案意味着我们从sizeof(float)*3(可能是6个字节)到sizeof(float*3) + padding + sizeof(float*)*3(可能是32个字节),因此对象的大小增加了5倍。
  • 与 setter/getter 相比,这种方法有什么好处?
  • @Nim:因此,诚实地警告 OP 是很诚实的。似乎他混淆了语言
  • @AndyT - 我认为他从他最后的评论中意识到了这一点.. :)
  • @AndyT:一个优点是从用户的角度来看语法稍微好一些(my_vec.x = 5 而不是my_vec.setX(5))。
【解决方案2】:

模板专业化怎么样?

template <int N> class Vector
{
  public:
  float data[N];
};

template <>
class Vector<1>
{
  public:
  union {
    float data[1];
    struct {
      float x;
    };
  };
};

template <>
class Vector<2>
{
  public:
  union {
    float data[2];
    struct {
      float x, y;
    };
  };
};


template <>
class Vector<3>
{
  public:
  union {
    float data[3];
    struct {
      float x, y, z;
    };
  };
};

class Vector3 : public Vector<3>
{
};

int main() {
  Vector3 v3;
  v3.x;
  v3.data[1];
};


编辑 好的,这是一种不同的方法,但它引入了一个额外的标识符。
template <int N> class Data
{
  public:
  float data[N];
};

template <> class Data<3>
{
  public:
  union {
    float data[3];
    struct {
      float x, y, z;
    };
  };
};

template <int N> class Vector
{
  public:
  Data<N> data;
  float sum() { }
  float average() {}
  float mean() {}
};

class Vector3 : public Vector<3>
{
};

int main() {
  Vector3 v3;
  v3.data.x = 0; // Note the extra "data".
  v3.data.y = v3.data.data[0];
};

【讨论】:

  • 您的新解决方案看起来不错,但是您确定这是明确定义的吗?我不确定这里的标准,我知道结构共享相同前导序列的具体规定,但我对叠加结构和数组一无所知。
  • 哈哈 - 是的!那很完美。使“命名”成员直接可用。不会增加班级的规模。正是我想要的。我的朋友,那是一种深奥的、黑暗的模板魔法!
  • 您现在不需要在每个专业中复制所有其他内容吗?
  • @Oli - 嗯...如果“所有其他东西”是指template &lt;int N&gt; class Vector 上定义的函数,那么...也许?我只测试了大小和成员访问权限,现在我正在尝试,我似乎无法通过Vector3.whatever() 访问Vector&lt;N&gt;::whatever() ...我现在正在寻找一种解决方法,但这可能会杀死这个答案- Vector&lt;N&gt; 类存在的全部原因是我只需要声明(和编码)公共向量函数一次。
  • 叹息...不,它没有。您无法从 Vector 的函数中访问 Data 的成员,这使得它作为函数存储库毫无用处 - 我猜是因为 Vector 不知道此时它将使用哪个 Data 实现?我会继续尝试,但我可能最终会诉诸丑陋的#include hack...
【解决方案3】:

这是一种可能性,摘自我对this question 的回答:

class Vector3 : public Vector<3>
{
public:
    float &x, &y, &z;

    Vector3() : x(data[0]), y(data[1]), z(data[2]) { }
};

这有一些问题,比如要求你定义自己的复制构造函数、赋值运算符等。

【讨论】:

  • +1 用于提及必要的自定义复制/分配运算符。
【解决方案4】:

您可以进行以下操作:

template <int N> struct Vector
{
    float data[N];
    //etc. (math, mostly)
};
struct Vector3_n : Vector<3>
{
    //Vector3-specific stuff, like the cross product
};
struct Vector3_a
{
    float x, y, z;
};
union Vector3
{
    Vector3_n n;
    Vector3_a a;
};

现在:

Vector3 v;
v.n.CrossWhatEver();
std::cout << v.a.x << v.a.y << v.a.z

您可以尝试匿名联合技巧,但这既不标准也不可移植。

但是请注意,使用这种联合很容易陷入未定义的行为,甚至没有注意到。不过,它可能大部分都可以工作。

【讨论】:

    【解决方案5】:

    我不久前写了一个方法(也允许 getter/setter),但它是一个不可移植的花哨的 hack,你真的不应该这样做。但是,我想我还是要把它扔掉。基本上,它使用一个特殊类型,每个成员都有 0 个数据。然后,该类型的成员函数获取this 指针,计算父Vector3 的位置,然后使用Vector3s 成员访问数据。此 hack 或多或少类似于参考,但不占用额外内存,没有重新安装问题,而且我很确定这是未定义的行为,因此它可能导致 nasal demons

    class Vector3 : public Vector<3>
    {
    public: 
        struct xwrap {
            operator float() const;
            float& operator=(float b);
            float& operator=(const xwrap) {}
        }x;
        struct ywrap {
            operator float() const;
            float& operator=(float b);
            float& operator=(const ywrap) {}
        }y;
        struct zwrap {
            operator float() const;
            float& operator=(float b);
            float& operator=(const zwrap) {}
        }z;
        //Vector3-specific stuff, like the cross product 
    };
    #define parent(member) \
    (*reinterpret_cast<Vector3*>(size_t(this)-offsetof(Vector3,member)))
    
    Vector3::xwrap::operator float() const {
        return parent(x)[0];
    }
    float& Vector3::xwrap::operator=(float b) {
        return parent(x)[0] = b;
    }
    Vector3::ywrap::operator float() const {
        return parent(y)[1];
    }
    float& Vector3::ywrap::operator=(float b) {
        return parent(y)[1] = b;
    }
    Vector3::zwrap::operator float() const {
        return parent(z)[2];
    }
    float& Vector3::zwrap::operator=(float b) {
        return parent(z)[2] = b;
    }
    

    【讨论】:

      【解决方案6】:

      结束一个老问题:不。这让我很难过,但你做不到。

      你可以靠近。比如:

      Vector3.x() = 42;
      

      Vector3.x(42);
      

      Vector3.n.x = 42;
      

      甚至

      Vector3.x = 42; //At the expense of almost quadrupling the size of Vector3!
      

      触手可及(请参阅其他答案 - 它们都非常好)。但我的理想

      Vector3.x = 42; //In only 12 bytes...
      

      只是不可行。如果您想从基类继承所有函数,则不是。

      最后,有问题的代码最终得到了相当大的调整——它现在是严格的 4 元向量(x、y、z、w),使用 SSE 进行向量数学,并且有多个几何类(Point、 Vector、Scale 等),因此出于类型正确性的原因,继承核心功能不再是一种选择。就这样吧。

      希望这可以为其他人节省几天的沮丧搜索!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-10
        • 2020-03-24
        • 1970-01-01
        • 2021-01-07
        • 1970-01-01
        • 2023-02-09
        • 2013-01-26
        • 1970-01-01
        相关资源
        最近更新 更多