Skip to main content
gamedev.net
🔒 Locked

[C++] Class instance relocation

Started by ToohrVyk 9 replies 1.1k views
Original Post
ToohrVyk
ToohrVyk
I have an instance of a class which contains no user-defined pointers, and is a POD in all ways except one: it uses virtual functions. Can the instance be relocated by a memory-wise copy function (such as malloc), or is there a way to make this relocation legal?
Enigma
Enigma
Is there a reason you can't just copy-construct/copy-assign it in the new location?

Enigma
ToohrVyk
ToohrVyk
Because I'm not in charge of the relocation code (which is inside a garbage collector), and said relocation code is C code which is not aware of such C++ considerations.
Enigma
Enigma
Fair enough. On x86 you should be safe to just memcpy it because every compiler I know of handles virtual function via an absolute pointer to the vtable, stored as the first member. For a bit of extra reassurance you could check the COM specification, since I believe this is required by COM. If it is, then you can pretty much count on it for all COM-compliant compilers, even if you're not using COM. I sincerely doubt that any of this is guaranteed by the C++ standard though, so you may sacrifice some portability, in particular I can say nothing about non x86 platforms, since I have almost no experience with them.

Enigma
ToohrVyk
ToohrVyk
And sadly, this code is meant to run on PocketPC, Mac OS X, Symbian OS, and several handheld consoles...

I think I'm going to have to do something hackish...
Kitt3n
Kitt3n
I think you are looking for "placement new"

=> a quick google turned up the following:

Standard C++ also supports placement new operator, which constructs an object on a pre-allocated buffer. This is useful when building a memory pool, a garbage collector or simply when performance and exception safety are paramount (there's no danger of allocation failure since the memory has already been allocated, and constructing an object on a pre-allocated buffer takes less time):
visit my website at www.kalmiya.com
ToohrVyk
ToohrVyk
I am already using placement new to create my objects on buffers that I get from the garbage collector (which is, in fact, a heap optimizer or something similar). But this doesn't allow the garbage collector to move my instances around.
SirLuthor
SirLuthor
As long as you have no pointers actually pointing to that object, it should be fine. Emphasis on 'should'. Just a simply memcpy should do the trick nicely. I'm using this extensively with my virtual-to-virtual address mapping system.
Free speech for the living, dead men tell no tales,Your laughing finger will never point again...Omerta!Sing for me now!
ToohrVyk
ToohrVyk
I agree, it seems to work on x86. But once the code is done, I will have to compile for a dozen platforms, and I'd rather not find out THEN that something is amiss.
Enigma
Enigma
About your only alternative (bar dropping the virtual functions) would be to implement your own virtual function support. It's doable, but the maintenance overhead is significantly higher and you should expect to lose some performance. You would also need to thoroughly investigate your target compilers on each platform and provide different code paths (via #defines) where virtual function implementations differ. Here's an example I hacked together for x86. It works (althought I hesitate to use such a strong word as "works") under Borland 5.6.4, gcc 3.3.1 and VC++ 7.1. It's also ugly as heck:
#include <iostream>#include <iterator>#include <boost/lexical_cast.hpp>template < typename MemberFunctionPointer >std::size_t getFunctionAddress(MemberFunctionPointer functionPointer){	return *reinterpret_cast< std::size_t * >(&functionPointer);}class Base{	public:		Base();		Base(Base const & base);		int virtualFunction();		Base & operator=(Base const & base);	protected:		Base(std::size_t * vtablePointer);		std::size_t * vtablePointer_;		int doVirtualFunction();	private:		static std::size_t vtable[1];};class Mid	:	public Base{	public:		Mid(int i);		Mid(Mid const & mid);		std::string virtualFunction2(int i);		Mid & operator=(Mid const & mid);	protected:		Mid(int i, std::size_t * vtablePointer);	private:		std::string doVirtualFunction2(int i);		static std::size_t vtable[2];		int i_;};class Derived	:	public Mid{	public:		Derived();	private:		int doVirtualFunction();		std::string doVirtualFunction2(int i);		static std::size_t vtable[2];};Base::Base()	:	vtablePointer_(reinterpret_cast< std::size_t * >(&Base::vtable)){}Base::Base(std::size_t * vtablePointer)	:	vtablePointer_(vtablePointer){}Base::Base(Base const & base)	:	vtablePointer_(reinterpret_cast< std::size_t * >(&Base::vtable)){}int Base::virtualFunction(){	std::size_t function = vtablePointer_[0];	return (this->*(*reinterpret_cast< int (Base::* *)() >(&function)))();}Base & Base::operator=(Base const & base){	vtablePointer_ = reinterpret_cast< std::size_t * >(&Base::vtable);	return *this;}int Base::doVirtualFunction(){	return 7;}std::size_t Base::vtable[1] = {getFunctionAddress(&Base::doVirtualFunction)};Mid::Mid(int i)	:	Base(reinterpret_cast< std::size_t * >(&Mid::vtable)),	i_(i){}Mid::Mid(int i, std::size_t * vtablePointer)	:	Base(vtablePointer),	i_(i){}Mid::Mid(Mid const & mid)	:	Base(reinterpret_cast< std::size_t * >(&Mid::vtable)),	i_(mid.i_){}std::string Mid::virtualFunction2(int i){	std::size_t function = vtablePointer_[1];#if defined(_MSC_VER)	return (this->*(*reinterpret_cast< std::string (Mid::* *)(int) >(&function)))(i);#else	return (*reinterpret_cast< std::string (* *)(Mid *, int) >(&function))(this, i);#endif}Mid & Mid::operator=(Mid const & mid){	vtablePointer_ = reinterpret_cast< std::size_t * >(&Mid::vtable);	i_ = mid.i_;	return *this;}std::string Mid::doVirtualFunction2(int i){	return "Mid: " + boost::lexical_cast< std::string >(i) + " with member variable: " + boost::lexical_cast< std::string >(i_);}std::size_t Mid::vtable[2] = {getFunctionAddress(&Base::doVirtualFunction), getFunctionAddress(&Mid::doVirtualFunction2)};Derived::Derived()	:	Mid(15, reinterpret_cast< std::size_t * >(&Derived::vtable)){}int Derived::doVirtualFunction(){	return 42;}std::string Derived::doVirtualFunction2(int i){	return "Derived: " + boost::lexical_cast< std::string >(i);}std::size_t Derived::vtable[2] = {getFunctionAddress(&Derived::doVirtualFunction), getFunctionAddress(&Derived::doVirtualFunction2)};int main(){	Base b;	Mid m(12);	Derived d;	Base * bp = &b;	std::cout << bp->virtualFunction() << '\n';	bp = &m;	std::cout << bp->virtualFunction() << '\n';	bp = &d;	std::cout << bp->virtualFunction() << '\n';	Mid * mp = &m;	std::cout << mp->virtualFunction2(99) << '\n';	mp = &d;	std::cout << mp->virtualFunction2(99) << '\n';	std::cout << b.virtualFunction() << '\n';	std::cout << m.virtualFunction() << '\n';	std::cout << m.virtualFunction2(99) << '\n';	std::cout << d.virtualFunction() << '\n';	std::cout << d.virtualFunction2(99) << '\n';	Mid m2 = d;	std::cout << m2.virtualFunction() << '\n';	std::cout << m2.virtualFunction2(99) << '\n';}

Enigma
ToohrVyk
ToohrVyk
Thank you for the many ideas in there. I guess I can also implement virtual functions in a platform-independent way (since I'm allowed to use virtual functions on objects that don't interact with the garbage collector), for instance having the vtable be a derived class. The performance loss might be acceptable.

I'm currently checking whether the compilers I use place the vptr at the beginning of the segment or not.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.