I am working with a collaborative code base on a physics experiment. The general use case, say for particles, goes something like this:
// Code_Version_v1:
class Particle {
public:
virtual float get_momentum() { return NAN; }
}
class Particle_v1 : public Particle {
private:
momentum;
public:
override float get_momentum() { return momentum; }
}
Then the experiment collects huge amounts of data on particles which are stored in a database (useing CERN's ROOT TTrees, which is another story altogether) and the client code generally has something like:
Particle* part = ttree->get_particle();
std::cout << part->get_momentum() << std::endl;
where the ttree object is returning instances of some inherited class of Particle.
If the code is updated to Code_Version_v2 with a new method added to the base class Particle, but then uses Particle_v1 from the prior code version, can it cause somevtable method call errors?
For example:
// Code_Version_v2 library
class Particle { // update with a new method
public:
virtual float get_momentum() { return NAN; }
virtual float get_mass() { return NAN; }
}
// class Particle_v1 is unchanged
class Particle_v2 : public Particle {
private:
float momentum;
float mass;
public:
override float get_momentum() { return momentum; }
override float get_mass() { return mass; }
}
And then the code does something like:
// Using Code_Version_v2 library
Particle* part = ttree->get_particle(0); // returns Particle_v1* generated with Code_Version_v1
std::cout << part->get_momentum() << std::endl; // now run with libraries from Code_Version_v2
Does the c++ standard guarantee that the compilers etc... will be smart enough to properly implement the vtable lookups or not? I realize this may be dependent on the ROOT implementation in how it saves and streams objects from the TTrees. I have some subtle bug which disappears with the addition of cout
statements, so it appears to be some kind fo memory corruption. All insights are appreciated. Thanks!
Per the standard one is not allowed to have two different definitions of an entity in the same program at all. Different here means that no single token in the definition may be different (plus even stricter requirements). That's part of the one-definition rule (ODR).
Adding a function to a class clearly violates that and therefore, generally it is required that you recompile every library/translation unit that includes the class definition in order to get any guarantee that the program will behave correctly.
The standard makes no guarantees on the program or compiler behavior if ODR is violated like that.
In practice requirements are a bit weaker. Compilers use a specified ABI for interfacing between binary code of multiple libraries or translation units. If one knows how this ABI is specified (and there are many relatively obvious commonalities given how compilers compile C++ to machine code), then one can get away with some modifications without recompiling libraries.
For example, adding a non-
virtual
non-special non-constructor/desctructor member function, that wouldn't have been chosen anywhere by overload resolution in the old code, is (in ABIs that I am aware of) not a problem, because it doesn't affect object layout, naming mangling or calling conventions, etc.However, adding a
virtual
member function, even a non-special one, does affect layout of the vtable and generally is not ABI compatible. If the class previously didn't have avirtual
member function it also affects the memory layout of the class itself.If may be possible to make this change smoothly, assuming you know exactly how the vtable is laid out per ABI and compiler. E.g. it may be possible that because of the ordering of the member functions, the pointer for
get_mass
is know to be appended at the end of the vtable, so that order and use of previous entries isn't affected. And if it is then known that onlyCode_Version_v2
emits the vtable forParticle_v2
, knowing about adding the additional entry, then it might be ok. But all of that is relying on good understanding of the particular ABI/compiler implementation, not any standard guarantees.It will also probably go wrong again as soon as some other class derived from
Particle
adds newvirtual
member functions, because then the same slot in the vtable will probably be considered to refer to different functions by different libraries. Again, to be sure one would have to read through the ABI specification of the vtable layouts and probably make some assumptions about the rest of the program's code that must be adhered to. In either way, it is not safe for unrestricted use of the class.