Sign in to follow this  

Virtual Inheritance Question (C++)

Recommended Posts

Erondial    172

I'm having a small problem with multiple inheritance in C++. I seem to be having two copies of the same variable, in the same class. As I understand it, this usually happens when a class tried to inherit two classes that inherit from the same parent class, and this can be fixed by making the intervening classes inherit virtually, so that the compiler structures the class fields and vtables to act like there's only one instance and path to the original class.

However, there still seems to be two copies of a single variable, in an object instance.

Here's how the classes are laid out. (please ignore the absolutely terrible names)

Object : Base
ObjectBase : virtual Object
Construct : ObjectBase
Ship : Construct

The variable, position, is declared in Object. When I try to modify the Position variable, using an Object* pointer, I modify one variable, but it doesn't have an affect when Construct tries to use it to draw where the object is; it uses another position variable.

Anyone have any idea of where I'm going wrong? Should I declare Construct's inheritance as virtual?

[Edited by - Erondial on November 11, 2010 2:20:50 AM]

Share this post

Link to post
Share on other sites
Palidine    1315
You don't have any multiple or diamond inheritance. Unless there is another position variable declared in ObjectBase/Construct/Ship then there is only one instance of position per instance. Posting your full class declarations would help.

The most likely thing is that something else is setting it or you are setting the position of a copy, not the original instance. Again need more code.

To diagnose, it's pretty simple with some advanced debugger use. One of the following things should help you diagnose:
1) After the position variable is set, create a hardware breakpoint on the memory address of the same variable. Odds are that something else is changing it.

2) Is position a public member variable? Hopefully it's not, then you could put a conditional breakpoint in the set function so it'll break if the function is called again with the same this pointer.


Share this post

Link to post
Share on other sites
wqking    761
That's multiple levels inheritance, not multiple inheritance...
You don't need virtual inheritance for it.
There is a bug about Position, maybe you redefined it in child class?

Share this post

Link to post
Share on other sites
Erondial    172
EDIT: Ignore the rest of this; I've found the problem.

My scripting system wasn't designed to handle objects with virtual inheritance, causing some pointers to be messed around; had actually nothing to do with the Ship/Camera itself. (go figure >.<) Maybe I should just rework things to avoid the diamond problem? It seems that it'd make half the things I need to do easier; and make the whole thing a whole lot faster (from what I understand, dynamic_cast is relatively expensive when compared to static_cast)

Sorry; I should've been more clear, I wrote that post relatively hastily, and didn't quite think it through as I should've.

The variable position isn't declared anywhere outside of Object or in any child classes (I had no problems before starting to use virtual inheritance).

Some context: Basically, I want to create a camera archetype in my engine, called OrbitCamera. OrbitCamera is then inherited into a type BaseOrbitCamera in the actual game; this is what's accessing the variable, through a pointer (I don't know if this'll make any difference)

The BaseOrbitCamera inheritance tree looks as follows;

Object : Base
Camera : virtual Object
OrbitCamera : virtual Camera
ObjectBase : virtual Object
BaseCamera : virtual Camera, virtual ObjectBase
BaseOrbitCamera : BaseCamera, OrbitCamera

BaseOrbitCamera, is accessing the Ship type through an Object pointer. It executes the following code upon each physics movement

void OrbitCamera::Step(float Delta)
if (OrbitObject) {
float3 ZAxis = Orientation.GetAxis(Z_AXIS);
float3 Destination = OrbitObject->Position + ZAxis;
float3 Change = Destination - Position;
Change.SetLength(Delta * OrbitSpeed);
Position += Change;

Now, the thing is, Destination is always showing up as (0,0,0) (for an orbit radius of 0), regardless of what I do; I've also tried displaying, using a print statement, the value of OrbitObject->Position, and it is indeed 0,0,0. However, using the debugger turns up a different value; the proper value, and the object is drawing itself at the appropriate location (using Construct's draw call). The memory addresses of the debugger's "Position" is also different from the printf's version of &OrbitObject->Position.

Again, really should've provided this information first; wasn't thinking. Sorry.

Infact, the fact that I wasn't using multiple inheritance with the Ship object has been causing me the most confusion; is it possible that something to do with virtual inheritance has created multiple copies of the variable?

For reference; classes leading up to ship look as follows:

class Base {
Base() { }
virtual ~Base() { }

virtual bool SetProperty(const Caelus::STRING& Name, const Caelus::GENERIC& Value) { return false; }
virtual Caelus::GENERIC GetProperty(const Caelus::STRING& Name) { return Caelus::GENERIC(); }

class Object : public Base {



float RotationCount;
unsigned char Type;
float Scale;
float Mass;
bool Alive;
Model* ModelObject;
Material* MaterialObject;
float3 Position;
float Radius;
float InnerRadius;
float3 SphereCenter;
float3 Box;
float3 BoxCenter;
matrix Orientation;
float3 Velocity;
float3 Acceleration;
float3 RotationalVelocity;
float3 RotationalAcceleration;
float4 Colour;
matrix CachedMatrix;
float3 PreviousRotation;
LIST<SpatialHierarchyNode*> Nodes;

virtual ~Object();

virtual void Draw(Camera* Eye);
virtual void Step(float Delta);
virtual bool IsInFrustum(Camera* Eye);

virtual bool Save(FILE* File) const;
virtual bool Load(FILE* File, SerializationDataset& Dataset);

void Rotate(float3 Axis, float Degrees);
void Rotate(float3 EulerAngles);
void SetScale(float NewScale);
void ResetScale(float NewScale);
void SetModel(Model* NewModel, Material* NewMaterial);

bool operator == (const Object& O) { return this == &O; }


class ObjectBase : public virtual Caelus::Object
REGISTER_CLASS(ObjectBase, Caelus::Object);

int3 Subsector;

unsigned int Drawn;
unsigned int Stepped;

bool Targeted;
unsigned char FadeState;
unsigned short CollisionFilter;
float Visibility;
ObjectBase* Owner;
//Caelus::LI<ObjectBase*> ActiveNode;

virtual ~ObjectBase();

ObjectBase* CollisionCheck(bool Respond);
ObjectBase* IsOccluded(float3 StartingPoint);
ObjectBase* IsOccluded(ObjectBase* StartingObject);
bool IsConstruct();
virtual void Destroy();
void Fade(float Speed);
void MoveSector(Sector* NewSector);
void Move(float3 NewPoint, int3 NewSubsector);
Sector* GetSector();
virtual bool IsInFrustum(Caelus::Camera* Eye); // Odd workaround. Doesn't seem to want to take Caelus::Object's function without a typecast.

virtual void Draw(Caelus::Camera* Eye) = 0;
virtual void Step(float Delta);
virtual bool CollisionResponse(ObjectBase* CollidedObject);

virtual bool Save(FILE* File) const;
virtual bool Load(FILE* File, Caelus::SerializationDataset& Dataset);

void Activate();
void Deactivate();

// Garbage collection.
virtual void InformDeleted(ObjectBase* Deleted) { }

// Should be the foundation of all space calculations.
static float3 PositionDelta(ObjectBase* First, ObjectBase* Second);
static float3 SubsectorDelta(ObjectBase* First, ObjectBase* Second);

class Construct : public virtual ObjectBase

static Construct* Player;

Character* Pilot;

Object* Orbited;

float PowerOutput;
float PowerInput;
float PowerNet;
float MaxThrust;
float MaxRotationThrust;

int* Cooldowns;

float EngineHeat;
float AfterburnerHeat;
float ShieldHeat;

float CurrentHeat;
float TotalHeat;

float CurrentMountSpace;
float CurrentCargoSpace;

unsigned char SystemPriority[6];
unsigned char WeaponCount;

float CurrentShields;
float MaxShields;

float MaxSpeed;
float MaxRotationSpeed;
float Counterthrust;
Vendor* ConstructVendor;

SubSystem<FrameAsset> Frame;
SubSystem<EngineAsset> Engine;
SubSystem<EngineAsset> RotationalEngine;
SubSystem<ReactorAsset> Reactor;
SubSystem<PowerCellAsset> PowerCell;
SubSystem<WeaponAsset>** Weapons;
SubSystem<ShieldAsset> Shield;
SubSystem<ArmorAsset> Armor;
SubSystem<SensorAsset> Sensor;
SubSystem<ArrayAsset> Array;
Caelus::LIST<ActiveEquipmentAsset*> ActiveEquipment;
Caelus::LIST<PassiveItem> PassiveEquipment;

bool Jumping;
float JumpProgress;
bool AlreadyJumped;
float3 JumpPosition;
int3 JumpSubsector;

virtual ~Construct();

virtual void Draw(Caelus::Camera* Eye) = 0;
virtual void Step(float Delta);

virtual void Explode();
virtual void Damage(ObjectBase* DamagingObject, float Magnitude);

virtual void Destroy();

void SetFrame(FrameAsset* NewData);
void SetEngine(EngineAsset* NewData);
void SetRotationalEngine(EngineAsset* NewData);
void SetReactor(ReactorAsset* NewData);
void SetPowerCell(PowerCellAsset* NewData);
bool AddWeapon(WeaponAsset* NewData, int MountPoint);
bool RemoveWeapon(int MountPoint);
void SetShield(ShieldAsset* NewData);
void SetArmor(ArmorAsset* NewData);
void SetSensor(SensorAsset* NewData);
void SetArray(ArrayAsset* NewData);
bool AddActive(ActiveEquipmentAsset* NewData);
bool RemoveActive(ActiveEquipmentAsset* NewData);
void AddPassive(PassiveEquipmentAsset* NewData, int Amount);
bool RemovePassive(PassiveEquipmentAsset* NewData, int Amount);

void UpdateEquipment();
void PowerDown();
void Thrust();
void Afterburner();
void FireWeapon(int MountPoint);
void RotationalThrust(unsigned char Axis, float Direction);
void RotationalStrafe(unsigned char Axis, float Direction);

float GetFuel();
float GetCharge();
float GetShields();
float GetArmor();

void Jump(int3 NewSubsector, float3 NewPosition);
bool CollisionResponse(ObjectBase* CollidedObject);

class Ship : public Construct
REGISTER_CLASS(Ship, Construct);

static Caelus::Material* DefaultMaterial;

virtual ~Ship();

virtual void Draw(Caelus::Camera* Eye);
virtual void Step(float Delta);

REGISTER_CLASS and REGISTER_ABSTRACT_CLASS, are just small macros that define things like Super, and are watchwords for a script that runs at compile time; they look as follows:

#define COMMON_CLASS_FEATURES(x)    static Caelus::MAP<Caelus::STRING, BaseProperty> Properties;    static byte InitializeClassAttributes();        virtual bool SetProperty(const Caelus::STRING& Name, const Caelus::GENERIC& Value)    {        if (Super::SetProperty(Name, Value))            return false;        Caelus::TI<Caelus::STRING, BaseProperty> IT = Properties(Name);        if (!IT || Value.Type != IT->Type)            return false;        Value.WriteMemory((void*)((int)this + IT->Offset));        return true;    }    virtual Caelus::GENERIC GetProperty(const Caelus::STRING& Name)    {        Caelus::GENERIC Temp = Super::GetProperty(Name);        if (Temp.Type != Caelus::TYPE_NONE)            return Temp;        Caelus::TI<Caelus::STRING, BaseProperty> IT = Properties(Name);        if (!IT)            return Caelus::GENERIC();        Caelus::GENERIC Value;        Value.ReadMemory((void*)((int)this + IT->Offset), IT->Type);        return Value;    }

#define REGISTER_CLASS(a, b) public: virtual void IsAbstract() { } typedef b Super; COMMON_CLASS_FEATURES(a);

#define REGISTER_ABSTRACT_CLASS(a, b) public: typedef b Super; virtual void IsAbstract() = 0; COMMON_CLASS_FEATURES(a);

I'm also well aware that this code looks like garbage (this is something I've been building over the years as I've been learning, so there are massive amounts of issues and unorthodox ugly code), and that there are extreme visibility as well as naming problems :p. Right now, I'm just trying to get things working.

Again, sorry for not posting all the details at the beginning.

EDIT: Also; strange, I seem to have trouble doing a dynamic_cast<Ship*>(OrbitObject) from BaseOrbitCamera's OrbitObject; it's returning NULL, despite the fact that it's pointing to a Ship.

[Edited by - Erondial on November 11, 2010 2:19:16 AM]

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this