【问题标题】:How virtual functions affect the way points are generated in OpenGL?虚函数如何影响 OpenGL 中生成点的方式?
【发布时间】:2020-12-04 17:31:18
【问题描述】:

我有一个项目,我使用点绘制一个三角形,它被称为 Sierpinski Gasket。为了实现这一点,我使用了一个包含 x 和 y 值的类 Vx。问题是如果任何方法是虚拟的,它会抛出不同的值,我想知道为什么。 虚函数如何影响以下 OpenGL 代码中点的生成方式?

我的向量类有以下声明:

    class Vx {
public:
    float x;
    float y;

    Vx();
    Vx(const float& value);
    Vx(const float& x, const float& y);
    Vx(const Vx& v);

    float getX() const;
    void setX(const float& x);
    float getY() const;
    void setY(const float& y);

    const float operator [] (int i) const;
    operator const float* () const;
    operator float* ();
    float& operator [] (int i);
};

std::ostream& operator<<(std::ostream& os, const Vx v);

Vx operator+(const Vx& p1, const Vx& p2);

Vx operator/(const Vx& p1, const Vx& p2);

它的实现是:

Vectors::Vx::Vx() : x(0), y(0)
{
}

Vectors::Vx::Vx(const float& value) : x(value), y(value)
{
}

Vectors::Vx::Vx(const float& x, const float& y) : x(x), y(y)
{
}

Vectors::Vx::Vx(const Vx& v) : x(v.x), y(v.y)
{
}

float Vectors::Vx::getX() const
{
    return this->x;
}

void Vectors::Vx::setX(const float& x)
{
    this->x = x;
}

float Vectors::Vx::getY() const
{
    return this->y;
}

void Vectors::Vx::setY(const float& y)
{
    this->y = y;

}

float& Vectors::Vx::operator[](int i)
{
    return *(&x + i);

}

const float Vectors::Vx::operator[](int i) const
{
    return *(&x + i);
}

Vectors::Vx::operator const float* () const
{
    return static_cast<const float*>(&x);
}

Vectors::Vx::operator float* ()
{
    return static_cast<float*>(&x);

}

std::ostream& Vectors::operator<<(std::ostream& os, const Vx v)
{
    os << "Vx: " << "x = " << v.x << ", y = " << v.y << std::endl;
    return os;
}

Vx Vectors::operator+(const Vx& p1, const Vx& p2)
{
    return Vx(p1.x + p2.x, p1.y + p2.y);
}

Vx Vectors::operator/(const Vx& p1, const Vx& p2)
{
    return Vx(p1.x / p2.x, p1.y / p2.y);
}

我的主类开始设置 OpenGL 以准备绘制,并且在某些时候我创建了一个包含以下数据的缓冲区:

void sierpinskiGasket() {

using namespace Vectors;

/*
Creamos la informacion para el triangulo
*/
const int numPoints = 5000;
Vx points[numPoints];
points[0] = Vx(0.0f, 0.0f);

Vx vertices[] = {
    Vx(-0.50f, -0.50f),
    Vx(0.50f, -0.50f),
    Vx(0.0f, 0.50f)
};

for (int i = 1; i < numPoints;i++) {
    int j = rand() % 3;

    points[i] = (points[i - 1] + vertices[j]) / 2;
}

/*
    Generamos el buffer para el triangulo
*/
unsigned int triangle_vbo;
glGenBuffers(1, &triangle_vbo);
glBindBuffer(GL_ARRAY_BUFFER, triangle_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
}

我得到的结果是下一个三角形(没关系):

但是,如果我将 Vx 类的任何方法更改为 virtual,接下来的事情就会发生:

【问题讨论】:

  • virtual 函数不太可能是问题,鉴于您显示的代码,因为您似乎没有使用任何间接(例如,通过指针或引用调用派生类的函数)基地)。更可能的问题是您将xy 成员视为数组的一部分,而它们不是数组的一部分,这将导致未定义的行为。我建议完全摆脱operator[]operator float*,或者用float xy[2]; 这样的实际数组替换xy
  • 看看Accessing struct data members via pointer arithmetic,这似乎几乎是这个问题的重复
  • 请参阅_vec2,了解如何合并类似数组和.x,.y 访问类

标签: c++ opengl


【解决方案1】:

具有虚拟方法的类具有隐式字段 - 指向 VMT(虚拟方法表)的指针。您可以通过打印sizeof(Vx) 使用和不使用虚拟方法来检查它。 C++ 标准未指定类和结构的内部表示,并且在编译器之间有所不同,因此您不应将具有虚拟方法的类传递给像 glBufferData 这样的函数。

【讨论】:

    【解决方案2】:

    经过一些研究,我找到了这个问题的更详细的答案,因为它解释了为什么虚函数会影响类成员的不同内存分配方式并引发不同的结果,此外还有一些来自 c++ 参考的文档解释了多态对象和内存分配也是。

    事实上,这是未定义的行为,以下问题的答案以简短且更好的方式解释了原因。

    https://stackoverflow.com/a/48300540/13716517

    但我还想从接下来的 2 个链接中添加 c++ 参考中所说的内容:

    https://en.cppreference.com/w/cpp/language/object

    https://en.cppreference.com/w/cpp/language/memory_model

    我们可以从哪里找到下一个文档:

    多态对象:声明或继承的类类型的对象 至少一个虚函数是多态对象。每个内 多态对象,实现存储附加信息 (在每个现有的实现中,除非经过优化,否则它是一个指针 out),由虚函数调用和 RTTI 特性使用 (dynamic_cast 和 typeid) 在运行时确定类型 对象是由哪个创建的,无论它使用什么表达式 in. 对于非多态对象,值的解释是 从使用对象的表达式确定,并且是 在编译时决定。

    内存位置:内存位置是标量类型的对象 (算术类型、指针类型、枚举类型或 std::nullptr_t) 或非零长度的最大连续位域序列 注意:语言的各种功能,例如引用和虚拟 功能,可能涉及额外的内存位置,而不是 程序可以访问,但由实现管理。

    考虑到所有这些,我们可以更详细地了解使用虚函数的未定义行为来自何处,了解它们在生成点时如何改变行为。

    【讨论】:

    • 无论您使用虚函数,您的代码都有未定义的行为,因为您正在对成员变量执行指针运算,这会导致未定义的行为。当您拥有 UB 时,由于添加虚函数等其他更改而导致的不同行为只是无数可能的结果之一。请重新阅读我的 cmets 关于您的问题。
    • 我理解你所说的,并提前感谢你抽出时间来回答,但我问的是虚函数如何导致这种未定义的行为,你没有解释原因。经过一番研究,我终于找到了原因,因为向我的类添加虚拟函数会添加一些额外的内存分配,这会导致我的 Vx 类成员变量的内存分配不像没有虚拟函数那样连续。即使我从我的方法中删除了 virtual 关键字,由于其他原因,它仍然是未定义的行为,但我想知道 virtual 部分。
    猜你喜欢
    • 2018-01-10
    • 1970-01-01
    • 1970-01-01
    • 2015-06-28
    • 2011-02-10
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多