Box2d 引擎为 std::vector
Box2d engine as std::vector
像往常一样使用“Physics a”创建box2d引擎。这在示例中工作正常。但是,如果我们将新实例添加为 std::vector 和 myphysics.push_back({});
,则在执行二进制文件后命令行上会显示以下错误:
“double free or corruption (!prev) Aborted (core dumped)”
我完全不知道消息是什么意思,也不知道 box2d 引擎的向量有什么问题。这是源代码:
// g++ -std=c++14 -lBox2D rrt.cpp
#include <Box2D/Box2D.h>
#include <vector>
class Physics {
public:
b2World world{b2Vec2(0.f, 9.8f)};
};
class RRT {
public:
std::vector<Physics> myphysics;
RRT() {
myphysics.push_back({});
//Physics a;
}
};
int main()
{
RRT myrrt;
}
问题本质上是 b2World
未设计为可复制。
我可以简单地使用以下代码块重现双重释放:
{
b2World woo{b2Vec2(0.f, 9.8f)};
b2World poo{woo};
}
这个问题可以通过源码分析来识别。
看了一下b2World
class, we can see that it has no user defined copy constructor and no user defined copy assignment method. Furthermore the code uses no mechanism to prevent the compiler from defining these. So the compiler follows the rules for special member functions,自动生成了拷贝构造函数和拷贝赋值运算符。这些自动生成的方法都不知道如何处理 b2World
导致分配的任何动态分配的内存(比如通过 b2BroadPhase
实例的组合)。这些自动生成的方法简单地调用组件实例的复制方法,在这种情况下,这会导致指向已分配内存的任何指针复制这些地址。然后在销毁时,像 b2BroadPhase
class 这样的析构函数调用 C-library free
函数为原始实例分配的内存,并再次为每个副本调用。
砰!!...双倍免费!
如果您想知道,这里有一些方法可以避免这个问题:
- 不要执行任何调用编译器定义的
b2World
复制构造函数或复制赋值运算符的操作。例如,不是使用 b2World
个实例的向量,而是将其设为指向 b2World
个实例的指针向量。您可以通过将 Physics
class 的 world
成员定义为类似以下内容来做到这一点:std::unique_ptr<b2World> world{std::make_unique<b2World>(b2Vec2(0.f, 9.8f))};
。如果您不介意重建 Box2D,您可能还想添加明确删除的这些特殊函数的定义,以帮助避免这些复制操作(只需添加行:b2World(const b2World& o) = delete; b2World& operator=(const b2World&) = delete;
到 class 定义中 b2World
在它的头文件中)。
- 更新 Box2D 代码以正确处理复制构造和复制分配。然而,在我看来,这比选项 1 更难正确地做到。
- 使用旨在支持复制构造和复制分配的备用物理引擎。无耻的插件:我偏向 PlayRho 这样的替代方案。它基本上是 Box2D 的衍生作品(分支)。 PlayRho也不是没有任何问题,但除了支持复制之外,它还有其他功能,单元测试覆盖率超过99%。
像往常一样使用“Physics a”创建box2d引擎。这在示例中工作正常。但是,如果我们将新实例添加为 std::vector 和 myphysics.push_back({});
,则在执行二进制文件后命令行上会显示以下错误:
“double free or corruption (!prev) Aborted (core dumped)”
我完全不知道消息是什么意思,也不知道 box2d 引擎的向量有什么问题。这是源代码:
// g++ -std=c++14 -lBox2D rrt.cpp
#include <Box2D/Box2D.h>
#include <vector>
class Physics {
public:
b2World world{b2Vec2(0.f, 9.8f)};
};
class RRT {
public:
std::vector<Physics> myphysics;
RRT() {
myphysics.push_back({});
//Physics a;
}
};
int main()
{
RRT myrrt;
}
问题本质上是 b2World
未设计为可复制。
我可以简单地使用以下代码块重现双重释放:
{
b2World woo{b2Vec2(0.f, 9.8f)};
b2World poo{woo};
}
这个问题可以通过源码分析来识别。
看了一下b2World
class, we can see that it has no user defined copy constructor and no user defined copy assignment method. Furthermore the code uses no mechanism to prevent the compiler from defining these. So the compiler follows the rules for special member functions,自动生成了拷贝构造函数和拷贝赋值运算符。这些自动生成的方法都不知道如何处理 b2World
导致分配的任何动态分配的内存(比如通过 b2BroadPhase
实例的组合)。这些自动生成的方法简单地调用组件实例的复制方法,在这种情况下,这会导致指向已分配内存的任何指针复制这些地址。然后在销毁时,像 b2BroadPhase
class 这样的析构函数调用 C-library free
函数为原始实例分配的内存,并再次为每个副本调用。
砰!!...双倍免费!
如果您想知道,这里有一些方法可以避免这个问题:
- 不要执行任何调用编译器定义的
b2World
复制构造函数或复制赋值运算符的操作。例如,不是使用b2World
个实例的向量,而是将其设为指向b2World
个实例的指针向量。您可以通过将Physics
class 的world
成员定义为类似以下内容来做到这一点:std::unique_ptr<b2World> world{std::make_unique<b2World>(b2Vec2(0.f, 9.8f))};
。如果您不介意重建 Box2D,您可能还想添加明确删除的这些特殊函数的定义,以帮助避免这些复制操作(只需添加行:b2World(const b2World& o) = delete; b2World& operator=(const b2World&) = delete;
到 class 定义中b2World
在它的头文件中)。 - 更新 Box2D 代码以正确处理复制构造和复制分配。然而,在我看来,这比选项 1 更难正确地做到。
- 使用旨在支持复制构造和复制分配的备用物理引擎。无耻的插件:我偏向 PlayRho 这样的替代方案。它基本上是 Box2D 的衍生作品(分支)。 PlayRho也不是没有任何问题,但除了支持复制之外,它还有其他功能,单元测试覆盖率超过99%。