开发者

Initialize a class members reference variable, as if it was a regular variable

Right now, I am writing a physics engine for a game I am developing. Often times, there is many repeated values when you combine a physics engine with a game engine. Such as variables denoting the position and rotation of an object. With most physics engines, you have to iterate through all the objects and update their positions according to the physics engine's object positions. So I thought it would be desirable to have the position and rotational values in the physic engines objects to be references to the game engines object's variables handling rotation and position. However, sometimes you want objects in the physics engine that don't correlate directly with objects in the game engine. (Invisible walls, joints). So you would need to treat the objects as regular member variables... So here is what I have.

struct object{
  float & xPosition;
  float & yPosition;
  float & zPosition;
  ...
  object(float & xPos, float& yPos, float& zPos):xPosition(xPos), yPosition(yPos), zPosition(zPos){}
  object():xPosition(*new float(0.0f)), yPosition(*new float(0.0f)), zPosition(*new float(0.0f)){}
};

However, this will lead to memory leaks, as those floats are not getting deleted. Do you have any suggestions about how I can achieve the desired beha开发者_开发技巧vior without the memory leak?

EDIT

I would rather not use boost. However I am not opposed to a solution that requires a template. Also, with this partially being a performance optimization, boost::shared_ptr, does not seem to be the right solution.


I would suggest you use a boost::shared_ptr to these position structures. This way, you don't have to worry about deletion and you can use it as either a pointer that is shared with the game engine object or as a stand-alone pointer.

Since there is overhead to these, you might want to limit the data-to-pointer ratio. In other words, don't keep a shared_ptr for each coordinate, but a shared_ptr to the position vector and a shared_ptr to the rotation rep., or, a shared_ptr to a homogeneous transform or frame (coordinate system, kinetic frame, or kinetostatic frame).

For example, you could have this:

class object {
  public:
    typedef boost::shared_ptr<Vector3D> pVector3D;
  private:
    pVector3D position;
  public:
    object(pVector3D aPos = pVector3D(new Vector3D(0.0,0.0,0.0))) : position(aPos) { };
};

The automatic and reference counting property of the shared_ptr will make it so that you won't need to worry about putting a delete statement (automatic) and there is no danger of the object disappearing from the game engine while the physics engine still needs those variables (reference counting guarantees that they will be deleted only when all objects that need them are deleted as well).

EDIT

I would rather not use boost. However I am not opposed to a solution that requires a template. Also, with this partially being a performance optimization, boost::shared_ptr, does not seem to be the right solution.

Well, shared_ptr/shared_array can also be found in the standard library technical report 1 (TR1) (so it's std::tr1::shared_ptr instead, so you don't need to use Boost to use those). As for performance optimization, that is why I recommend a fairly high ratio of data-to-pointer. The overhead of shared_ptr is mainly a memory overhead and some indirection during deletion and copying (which are two operations that are not done so often), I don't think there is much overhead in accessing the data it points to as compared to a regular pointer or a reference. You have to accept that, even by using references, you are trading data-copying overhead with data-access indirection overhead (you are also sacrificing memory locality, which is a big deal!). I would say that the performance drop related to memory locality being broken is going to be far worse than just the indirection alone. So, when it comes to accessing elements, IMO, shared_ptr, raw pointers and references will have very little performance difference. In many algorithms that uses these shared variables, you would probably be better off copying the data pointed to by the pointer/reference to local variables, calculate with and on those local variables, and then copying them back to the memory pointed to by the pointer/reference.

I recommend that you do some tests of your own on the performance when using either solutions (using shared_ptr, using references or raw pointers, and copying the data between the game engine and physics engine) and see for yourself, you might be surprised at what you find.

EDIT2

Have you considered using multiple inheritance scheme. This problem can probably be very well served with a diamond inheritance scheme:

class PositionedObject {
  protected:
    float Position[3];
  public:
    PositionedObject(float x,float y, float z) { Position[0] = x; ... };
    virtual ~PositionedObject() { };
};

class VisibleObject : virtual public PositionedObject { //note that the "virtual" keyword is critical here.
  ... rendering-related code ... i.e. the game-engine side of the implementation
};

class RigidBody : virtual public PositionedObject { //again "virtual" is very important.
  ... physics code here ...
};

class MyObject : public VisibleObject, public RigidBody {
  ... code specific to MyObject ...
};

This above schemes make the physics object and the game-engine object share the same position data (with little indirection, little memory-overhead and little memory-locality problems). I'm pretty sure this would be more efficient than any other scheme, but arguments about performance can only be answered with test results that you will have to do on your own if performance is really your prime concern (make sure that you are not doing premature optimization!).


You could use Boost shared_array to share the XYZ coordinates among an arbitrary number of objects:

struct object {
    boost::shared_array<float> coords;

    object(const boost::shared_array<float>& coords_)
        : coords(coords_)
    {
    }

    object()
        : coords(new float[3])
    {
        coords[0] = coords[1] = coords[2] = 0.f;
    }
}

The shared_array and shared_ptr templates employ reference counting to ensure that the memory is deleted after the last reference to it is destroyed. Copy-constructing a shared_array or shared_ptr adds one to the reference count and destroying a shared_array or shared_ptr subtracts one from the reference count. When the reference count reaches 0, the shared memory is deleted.


Do what you're doing now, just keep an extra bool variable to indicate whether your memory was allocated or not. Then, in the destructor, you can call delete after checking that value, i.e.

struct object{
  float & xPosition;
  float & yPosition;
  float & zPosition;

  object(float & xPos, float& yPos, float& zPos)
    :xPosition(xPos),
     yPosition(yPos),
     zPosition(zPos),
     allocated(false)
  {}
  object()
    :xPosition(*new float(0.0f)),
     yPosition(*new float(0.0f)),
     zPosition(*new float(0.0f)),
     allocated(true)
  {}

  ~object() {
    if(allocated) {
      delete &xPosition;
      delete &yPosition;
      delete &zPosition;
    }
  }

private:
  bool allocated;
};


One very simple way...

struct object
{
    float& xPosition;
    float& yPosition;
    float& zPosition;
    float x, y, z;
    ...

    object(float& xPos, float& yPos, float& zPos)
      : xPosition(xPos), yPosition(yPos), zPosition(zPos)
    { }

    object() : xPosition(&x_), yPosition(&y_), zPosition(&z_), x(0), y(0), z(0)
    { }
};

...you end up halving both references and variables when only one is needed for any given object, but the hassle and overheads of managing that elsewhere could easily cost more anyway (whether in memory or code bloat from template instantiations).

If you did want to stick to something closer to what you had yourself, you can simply add a boolean to track whether the memory needs to be deleted. (EDIT: PigBen just posted code for such). Personally, I'd recommend using one new float[3] so you're doing one memory allocation instead of three... not only will that be faster but it may waste less memory in heap management too.


A customized bookkeeping system for pointers would be appropriate. You can pass coordinates to the physics system as pointers, and specify with a boolean if the physics engine should add them to a bookkeeping structure - a list of pointers would be the simplest choice. All the memory that is dynamically reserved for the physics engine could then be freed by iterating over the list. Alternatively you could handle memory in each object, but that has the drawback of adding mostly unnecessary data to the memory area you are using to do the actual calculations. Of course, adding data to object might be the only sensible solution if you need to manage memory per-object instead of per-level. The freeing / reserving operations are more complex in this approach, but you save some processing while the engine is running. Here's a solution with only one float pointer in object, which would point to a three-float array:

class physics_system_t {
public:
    std::vector<float *> my_arrays;
} physics_system;

class object {
public:
    object(float * coords_ptr, bool manage_memory) : coords_array(coords_ptr)
    {
        if (manage_memory) {
            physics_system.my_arrays.push_back(coords_ptr);
        }
    }
    float *coords_array;
};

EDIT: Here's one more alternative that hasn't yet been proposed. Use an union to store your data as either local variables or references, and have a boolean to toggle between the two.

class object {
public:
    object(float & x, float& y, float& z, bool use_refs);
    union {
        struct refs_t {
            float &xr;
            float &yr;
            float &zr;
        } refs;
        struct vals_t {
            float x;
            float y;
            float z;
        } vals;
    }
    bool use_refs;
};

This would preserve data locality when the memory is handled by object, doesn't need extra space, and you don't need to do any manual deleting. Of course, performance might still be a problem, since you need to choose whether to use the references or the variables each time you access them. You'd also need to be careful with that, as you don't want to accidentally use the references when you mean to use the variables. The gains might not be worth this problem and the increased code size.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜