ausdruck durch konstanten ausdruck ersetzbar?


  • Mod

    für zwei beliebige klassen T und U, wobei U direkt oder mittelbar abgeleitet ist (aber nicht virtuell), ist für einen pointer u auf ein beliebiges (unter-)objekt vom typ U:

    T* t = p;
    prtdiff_t diff = reinterpret_cast< char* >( u ) - reinterpret_cast< char* >( t );
    

    diff eine konstante, die der compiler kennt (andernfalls wäre static_cast in downcasts grundsätzlich unsicher). gibt es eine möglichkeit, diff as konstanten ausdruck zu erhalten (ich hatte an offsetof gedacht, aber die basis klasse ist ja kein echter member)?



  • Das geht nicht!
    Es sei denn T und U sind Normalteiler
    bzw. polymorph zueinander!



  • Das klingt ziemlich brutal und ich frage mich, was du damit vor hast 😃

    Rein theoretisch sollte der Ausdruck aber schon zur Compilezeit konstant sein für zwei vollständig benannte Klassen T und U. Wie man diesen aber legal bekommt, geschweige denn legal verwendet, wüsste ich aber nicht.


  • Mod

    sigridweyl schrieb:

    Das geht nicht!
    Es sei denn T und U sind Normalteiler
    bzw. polymorph zueinander!

    ich sagte bereits, dass U direkt oder mittelbar von T abgeleitet sein soll. T und U müssen keineswegs polymorph sein, damit eine wandlung von U* nach T* legal ist (auch wenn das in dem code, um den es mir geht, eine voraussetzung ist).

    und ja es ist brutal, es geht hier auch um die (möglicherweise ganz evtl 🙂 mögliche) optimierung eines templates, genauer den auto_ptr, den ich hier:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-127044.html
    gebastelt habe. in diesem falle ist auto_ptr_ref zwar sicher aber auch im allgemeinen sehr ineffizient. das kann man für zumindest für den fall, dass keine konvertierung stattfindet, optimieren (die signatur ist dann nicht mehr ganz standard-konform, an der nutzbarkeit des templates ändert sich aber nichts):

    template<typename Target, bool same_type = false>
    	class auto_ptr_ref
    	{
    		template<typename>
    		friend class auto_ptr;
    		explicit auto_ptr_ref(void** pptr, Target* ptr) throw()
    			: pptr_( pptr ), ptr_( ptr )
    		{}
    		Target* release() const
    		{
    			*pptr_ = 0;
    			return ptr_;
    		}
    		void** pptr_;
    		Target* ptr_;
    	};
    	template<typename Target>
    	class auto_ptr_ref< Target, true >
    	{
    		template<typename>
    		friend class auto_ptr;
    		explicit auto_ptr_ref(void** pptr, Target*) throw()
    			: pptr_( pptr )
    		{}
    		Target* release() const
    		{
    			Target* ptr = static_cast< Target* >( *pptr_ );
    			*pptr_ = 0;
    			return ptr;
    		}
    		void** pptr_;
    	};
    // ...
    
    		auto_ptr(auto_ptr_ref< Pointee, false > rhs) throw()
    			: ptr_( rhs.release() )
    		{}
    		auto_ptr(auto_ptr_ref< Pointee, true > rhs) throw()
    			: ptr_( rhs.release() )
    		{}
    // ...
    		template<typename Target>
    		operator auto_ptr_ref< Target, boost::is_same< Target, Pointee >::value >() throw()
    		{
    			// require complete type here
    			// (implies that Target is complete or else conversion fails anyway)
    			(void)sizeof Pointee;
                            // has_virtual_destructor works only with VC8, is_polymorphic is a weaker requirement
    			BOOST_STATIC_ASSERT( ( \
    				boost::is_same< Target, Pointee >::value \
    					|| boost::is_polymorphic< Target >::value \
    						&& boost::has_virtual_destructor< Target >::value ) );
    			return auto_ptr_ref< Target, boost::is_same< Target, Pointee >::value >( &ptr_, get() );
    		}
    
    // etc...
    

    wenn es gelänge, die offset-verschiebung beim kompilieren zu ermitteln (oder wenigstens alle fälle, in denen keine verschiebung stattfindet, insbesondere der gewöhnliche single-inheritance fall) - könnte die optimierung auf diese fälle erweitert werden.


  • Mod

    nocheinmal zur motivation:
    der auto_ptr von vc8 hat einen tödlichen defekt, wie ich schon angedeutet habe,
    in vc7.1 ist der auto_ptr komplett kaputt was zuweisung von kompatiblen rvalues angeht.

    beiden vc++ versionen als auch alle gcc versionen (d.h. die standardbibliothek, die jeweils dazu gehört) kommen nicht mit mehrfachvererbung zurecht:

    #include <iostream>
    #include <memory>
    
    struct A
    {
    	virtual ~A() {}
    	int i;
    };
    struct B
    {
    	virtual ~B() {}
    	int i;
    };
    struct C : A, B
    {
    	C()
    	{
    		A::i = 1;
    		B::i = 2;
    	}
    };
    
    using namespace std;
    
    auto_ptr< C > foo() { return auto_ptr< C >( new C ); }
    
    int main()
    {
    	C* c = new C;
    	A* a = c;
    	B* b = c;
    	cout << "raw:  " << a->i << "  " << b->i << endl;
    	delete c;
    	auto_ptr< A > aa( foo() );
    	cout << "auto: " << aa->i << "  ";
    	auto_ptr< B > ab( foo() );
    	cout << ab->i << endl;
    }
    

    eine implementierung, die funktioniert, ist z.b. die von STLport, dort wird auch ein doppel-pointer ansatz verfolgt. allerdings ist dieser ansatz soboptimal im gewöhnlichen fall. zu erwähnen wäre noch, dass diese optimierung tatsächlich eine rolle spielt - z.b. wird visual c++ eine funktion, die ein objekt mit nicht-trivialen destruktor - wie z.b. auto_ptr - zurückgibt, niemals inlinen (wenn C++ exceptions erlaubt sind), was zur folge hat, dass auch das auto_ptr_ref objekt nicht eliminiert werden kann. jede vereinfachung dieses proxies erhöht die qualität das endgültigen assembler codes.



  • ich weiß nicht, was du dir erwartest?
    als ausgabe bekomme ich

    programm schrieb:

    raw: 1 2
    auto: 1 2


  • Mod

    mit g++, ja? ok, ich hab noch mal in die quellen geschaut - es klappt tatsächlich mit gcc, was mehrfachvererbung angeht. allerdings ist das ganze nicht mehr standardkonform, denn:

    int main()
    {
        auto_ptr<C> ac(foo());
        ac.operator auto_ptr_ref<C>();
        cout << ac->A::i; // oops
    }
    

    zugegeben, sowas schreibt man ja nicht, aber es ist durchaus zulässig.


Anmelden zum Antworten