Forum: PC-Programmierung C++: this-Typ für ein Member-Template automatisch erkennen?


von cppbert (Gast)


Lesenswert?

Ich habe so eine Art Windows COM Dll-Schnittstelle die ich mit 
Ref-Counting (wegen unterschiedlicher CRTs) austatten möchte

Ich möchte das RefCounting nicht ueber eine Ableitung implementierung da 
ich virtuelle Destruktoren vermeiden will, also habe ich so eine Art 
Mixin-Macro gemacht welche mir für eine Klasse das Ref-Counting 
implementiert

Funktioniert alles, aber es ist ein wenig unschön das ich in der Klasse 
beim Macro-Aufruf (ganz unten im Code) noch mal den Klassentyp angeben 
muss - kann ich den irgendwie automatisch dedukten?

das Macro darf ruhig weiter so existieren nur das CLASS-Parameter ist 
ein wenig nervig

Wie gesagt: Ohne Ableitung von einer Implementierung - nur Interfaces 
sind als Ableitung erlaubt

Ideen?

godbolt: https://gcc.godbolt.org/z/K3n1ch
1
#include <atomic>
2
#include <cassert>
3
4
template<typename Class>
5
class RefCounter
6
{
7
private:
8
  std::atomic<int> m_counter{ 1 };
9
public:
10
    int add_ref() noexcept
11
    {        
12
      return ++m_counter;
13
    }    
14
    
15
    int release(Class* this_) noexcept
16
    {
17
        assert( m_counter > 0 );
18
        if( --m_counter == 0 )
19
        {
20
            delete this_;
21
        }
22
        return m_counter;
23
    }
24
};
25
26
#define MY_API // DLL import/export
27
28
class IRefCounted
29
{
30
   virtual int MY_API add_ref() = 0;
31
   virtual int MY_API release() = 0;
32
};
33
34
#define REF_COUNTED(CLASS) \
35
private: \
36
    RefCounter<CLASS> m_ref_counter; \
37
public: \
38
    int MY_API add_ref() noexcept override { return m_ref_counter.add_ref(); } \
39
    int MY_API release() noexcept override { return m_ref_counter.release(this); } \
40
41
class TestClass: public IRefCounted
42
{
43
  REF_COUNTED(TestClass);
44
};

von cppbert (Gast)


Lesenswert?

Es gibt auch noch ein NON_REF_COUNTED Macro - das ist aber einfacher
1
#define NON_REF_COUNTED() \
2
public: \
3
    int MY_API add_ref() noexcept override { return 1; } \
4
    int MY_API release() noexcept override { return 1; } \

von Programmierer (Gast)


Lesenswert?

Warum nicht std::shared_ptr oder ein eigener Smart Pointer? Referenz 
Zählung ohne Smart Pointer ist gefährlich; schnell hat man mal einen 
Pointer auf die Instanz kopiert ohne add_ref aufzurufen, bzw. einen 
Pointer gelöscht ohne release aufzurufen.
Es gibt auch noch boost::intrusive_ptr mit etwas weniger Overhead.

cppbert schrieb:
> Ich möchte das RefCounting nicht ueber eine Ableitung implementierung da
> ich virtuelle Destruktoren vermeiden will,

Ein Eintrag mehr in der vtable macht nicht den großen Unterschied... 
Wenn du per CRTP arbeitest (der Basisklasse per template Parameter die 
eigene Klasse übergeben), kann die Basis das Objekt löschen in dem man 
"delete static_cast<Derived*>(this)" macht. Vermeidet Makros und 
virtuelle Destruktoren. Aber wie gesagt gehört das IMO in einen Smart 
Pointer.

von cppbert (Gast)


Lesenswert?

Programmierer schrieb:
> Warum nicht std::shared_ptr oder ein eigener Smart Pointer?

Template durch eine Dll Schnittstelle ist denke ich keine gute Idee, 
noch dazu will ich Debug und Release Builds mischen können

> Referenz
> Zählung ohne Smart Pointer ist gefährlich; schnell hat man mal einen
> Pointer auf die Instanz kopiert ohne add_ref aufzurufen, bzw. einen
> Pointer gelöscht ohne release aufzurufen.
> Es gibt auch noch boost::intrusive_ptr mit etwas weniger Overhead.

Das ist nur die nackte pure Pointer Schnittstelle, die ja auch beim COM 
genau so aussieht, erst darueber kommt bei mir ein SmartPointer ala 
CComPtr

>
> cppbert schrieb:
>> Ich möchte das RefCounting nicht ueber eine Ableitung implementierung da
>> ich virtuelle Destruktoren vermeiden will,
>
> Ein Eintrag mehr in der vtable macht nicht den großen Unterschied...
> Wenn du per CRTP arbeitest (der Basisklasse per template Parameter die
> eigene Klasse übergeben), kann die Basis das Objekt löschen in dem man
> "delete static_cast<Derived*>(this)" macht. Vermeidet Makros und
> virtuelle Destruktoren. Aber wie gesagt gehört das IMO in einen Smart
> Pointer.

Das hatte ich auch schon, aber zu einer Zeit als ich noch 
Mehrfachableitung supportet habe das sah der einfache cast sehr 
kompliziert aus, ich schau mir noch mal den code an

von cppbert (Gast)


Lesenswert?

Und natürlich kann ich auch gleich alles mit den Macros implementieren, 
aber ich wollte versuchen den Macro-Code Umfang auf ein minimum zu 
reduzieren

von Programmierer (Gast)


Lesenswert?

cppbert schrieb:
> Template durch eine Dll Schnittstelle ist denke ich keine gute Idee,

Deine TestClass enthält auch ein template. Ob nun das template Teil des 
Typs oder der Typ selbst ist macht keinen Unterschied...

von cppbert (Gast)


Lesenswert?

Programmierer schrieb:
> cppbert schrieb:
>> Template durch eine Dll Schnittstelle ist denke ich keine gute Idee,
>
> Deine TestClass enthält auch ein template. Ob nun das template Teil des
> Typs oder der Typ selbst ist macht keinen Unterschied...

Ist es nicht, das IRefCounted interface ist ja das einzige das durch die 
Dll schnittstelle sichtbar ist (was den refcounting teil angeht) ein 
shared_ptr und auch ein boost intrusive pointer kann die Dll 
schnittstelle nicht sicher durchdringen, gehen tut es schon irgendwie 
undefiniert, aber spätestens bei debug/release Vermischung ist dann 
Schluss, oder verstehe ich dich falsch?

von Programmierer (Gast)


Lesenswert?

cppbert schrieb:
> Ist es nicht, das IRefCounted interface ist ja das einzige das durch die
> Dll schnittstelle sichtbar ist

Und wie ruft man dann irgendwelche Funktionen auf TestClass auf, greift 
auf Member Variablen zu, und legt Instanzen davon an, wenn man lediglich 
das Refcounting Interface sieht? Das Layout der vtables ist ja auch 
nicht wirklich fix, erst recht wenn man Funktionen hinzufügt.

cppbert schrieb:
> aber spätestens bei debug/release Vermischung ist dann Schluss, oder
> verstehe ich dich falsch?

Nö, so variabel sind die ABIs nicht. Probleme gibt es wohl erst wenn man 
unterschiedlich C++ Standard Libraries nutzt (z.B. GCC vs MSVC).

von cppbert (Gast)


Lesenswert?

Programmierer schrieb:
> cppbert schrieb:
>> Ist es nicht, das IRefCounted interface ist ja das einzige das durch die
>> Dll schnittstelle sichtbar ist
>
> Und wie ruft man dann irgendwelche Funktionen auf TestClass auf, greift
> auf Member Variablen zu, und legt Instanzen davon an, wenn man lediglich
> das Refcounting Interface sieht? Das Layout der vtables ist ja auch
> nicht wirklich fix, erst recht wenn man Funktionen hinzufügt.

Ueber andere Interfaces die in diesem Beispiel fehlen

> cppbert schrieb:
>> aber spätestens bei debug/release Vermischung ist dann Schluss, oder
>> verstehe ich dich falsch?
>
> Nö, so variabel sind die ABIs nicht. Probleme gibt es wohl erst wenn man
> unterschiedlich C++ Standard Libraries nutzt (z.B. GCC vs MSVC).

Aber innerhalb von MSVC gibt es dafuer keine Garantie, ist mir zu 
unsicher für mein Projekt, und ich werde auch unterschiedliche Kompiler 
und Versionen verwenden - aber darum ging es mir bei meiner Frage ja 
auch gar nicht

von mh (Gast)


Lesenswert?

cppbert schrieb:
> Ueber andere Interfaces die in diesem Beispiel fehlen

Wie übergibst du diese Objekte, dass du über verschiedene Interfaces 
darauf zugreifen kannst?

von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> Ueber andere Interfaces die in diesem Beispiel fehlen
>
> Wie übergibst du diese Objekte, dass du über verschiedene Interfaces
> darauf zugreifen kannst?

Genau so wie in COM, es gibt eine Art QueryInterface

von mh (Gast)


Lesenswert?

cppbert schrieb:
> Genau so wie in COM, es gibt eine Art QueryInterface

Wie wird das praktisch umgesetzt? Mit rtti/dynamic cast?

von Sven B. (scummos)


Lesenswert?

Wie wär's mit sowas in der Richtung:
1
#define ... \
2
  private: \
3
    auto* __getThis() const { return this; } \
4
    RefCounter<decltype(*__getThis())> counter; \
5
    ...

Hab's nicht ausprobiert, aber müsste ungefähr so gehen?

: Bearbeitet durch User
von cppbert (Gast)


Lesenswert?

mh schrieb:
> cppbert schrieb:
>> Genau so wie in COM, es gibt eine Art QueryInterface
>
> Wie wird das praktisch umgesetzt? Mit rtti/dynamic cast?

Jedes Interface hat eine GUID, so wie bei COM und die Klasse 
implementiert den caster QueryInterface selber also mit einem internen 
static_cast, kein rtti/dynamic_cast

am liebsten wäre es mir ja irgendeine gute C++ multiplatform COM 
Bibliothek zu nutzen, aber scheinbar gibt es da nichts - also eine Lib 
die Komponentenentwicklung mit verschiedenen Kompilern, Sprachen und 
unterschiedlichen Heapmanagern erlaubt

von Sven B. (scummos)


Lesenswert?

Ok, mein Vorschlag oben geht natürlich nicht. Das hier geht auch nicht, 
aber hier verstehe ich nicht wieso:
1
private: \
2
    auto* __getThis() const { return this; } \
3
    RefCounter<std::result_of<&__getThis>::type> m_ref_counter; \

Das gibt
1
<source>:44:3: error: function '__getThis' with deduced return type cannot be used before it is defined

Versteh ich nicht. Ist doch defined.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

cppbert schrieb:
> am liebsten wäre es mir ja irgendeine gute C++ multiplatform COM
> Bibliothek zu nutzen, aber scheinbar gibt es da nichts

So etwas ist in C++ einfach notorisch kompliziert. Mit z.B. Java erspart 
man sich eine Menge Frust...

cppbert schrieb:
> Aber innerhalb von MSVC gibt es dafuer keine Garantie

Doch, ich meine solange man alles mit MSVC macht sollte das Übergeben 
von shared_ptr über Dll-Grenzen gar kein Problem sein. Oder du 
implementierst einfach deinen eigenen shared_ptr der genau so 
funktioniert, dann kannst du das ABI festlegen.

von cppbert (Gast)


Lesenswert?

Programmierer schrieb:
> cppbert schrieb:
>> am liebsten wäre es mir ja irgendeine gute C++ multiplatform COM
>> Bibliothek zu nutzen, aber scheinbar gibt es da nichts
>
> So etwas ist in C++ einfach notorisch kompliziert. Mit z.B. Java erspart
> man sich eine Menge Frust...

100% ACK, in Java oder .Net kommt vieles davon ab Werk, aber andere 
Kompiler/Sprachen Integration ist da auch manchmal sehr müllig

>
> cppbert schrieb:
>> Aber innerhalb von MSVC gibt es dafuer keine Garantie
>
> Doch, ich meine solange man alles mit MSVC macht sollte das Übergeben
> von shared_ptr über Dll-Grenzen gar kein Problem sein. Oder du
> implementierst einfach deinen eigenen shared_ptr der genau so
> funktioniert, dann kannst du das ABI festlegen.

Den habe ich schon, nur nicht an dieser tiefen Stelle

von cppbert (Gast)


Lesenswert?

Ich hatte Tomaten auf den Augen
1
class RefCounter
2
{
3
private:
4
  std::atomic<int> m_counter{ 1 };
5
public:
6
    int add_ref() noexcept
7
    {        
8
      return ++m_counter;
9
    }    
10
    
11
    template<typename Class> // <------- !!!!
12
    int release(Class* this_) noexcept
13
    {
14
        assert( m_counter > 0 );
15
        if( --m_counter == 0 )
16
        {
17
            delete this_;
18
        }
19
        return m_counter;
20
    }
21
};

loest mein Problem - es ist ja nur die ::release-Methode
welche den Klassen-Typ für das delete "kennen" muss - dafür reich ganz 
einfach eine Template-Methode - man muss nur ein paar Tage was völlig 
anderes machen und schon...

von Heiko L. (zer0)


Lesenswert?

cppbert schrieb:
> template<typename Class> // <------- !!!!
>     int release(Class* this_) noexcept
>     {
>         assert( m_counter > 0 );
>         if( --m_counter == 0 )
>         {
>             delete this_;
>         }
>         return m_counter;
>     }

Sieht fragwürdig aus, m_counter nach dem delete zu benutzen.

von cppbert (Gast)


Lesenswert?

Heiko L. schrieb:
> cppbert schrieb:
>> template<typename Class> // <------- !!!!
>>     int release(Class* this_) noexcept
>>     {
>>         assert( m_counter > 0 );
>>         if( --m_counter == 0 )
>>         {
>>             delete this_;
>>         }
>>         return m_counter;
>>     }
>
> Sieht fragwürdig aus, m_counter nach dem delete zu benutzen.

richtig erkannt - das assert ist auch sinnfrei wenn delete schon
mal ausgeführt wurde, lieber in meinen lokalen Tests auf den ASAN oder 
den MSVC DebugHeap vertrauen

also so
1
template<typename Class> // <------- !!!!
2
int release(Class* this_) noexcept
3
{
4
  --m_counter;
5
  if( m_counter == 0 )
6
  {
7
    delete this_;
8
    return 0;
9
  }
10
  return m_counter;
11
}

von mh (Gast)


Lesenswert?

Dann bleibt noch der zweifelhafte Rückgabewert von release. Der Wert ist 
ziemlich nutzlos, weil er nicht "aktuell" ist.

Und natürlich die race condition, da zwischen --m_counter und 
m_counter==0 alles Mögliche passieren kann.

Ich rate dir dringend davon ab, einen RefCounter selbst zu 
programmieren. Da sind schon Personen dran gescheitert, die in sachen 
cpp und concurrency deutlich mehr Erfahrung haben.

: Wiederhergestellt durch Moderator
von cppbert (Gast)


Lesenswert?

mh schrieb:
> Dann bleibt noch der zweifelhafte Rückgabewert von release. Der Wert ist
> ziemlich nutzlos, weil er nicht "aktuell" ist.

das ist einen Schnittstellenvorgabe - ich brauche den auch nicht 
wirklich

> Und natürlich die race condition, da zwischen --m_counter und
> m_counter==0 alles Mögliche passieren kann.

hast ja recht - concurreny-löchrig wie ein Schweizer Käse
und ja mir ist 100% klar was du meinst - ich brauch nur genug Threads 
die das Objekt sharen/unshared, ein bisschen Glück und dann passiert die 
Appokalypse

> Ich rate dir dringend davon ab, einen RefCounter selbst zu
> programmieren. Da sind schon Personen dran gescheitert, die in sachen
> cpp und concurrency deutlich mehr Erfahrung haben.

da geben ich dir auch recht - und meine Beispiele hier sind reichlich 
dilettantisch - es ging mir im Grund nur um den Klass-Type weil ich
von einem Haufen MACRO-Code weg kommen will

ich würde den std::shared_ptr (boost intrusive_ptr) verwenden um das 
DLL-Interface zu implementieren - so war die Implementierung auch schon

von cppbert (Gast)


Lesenswert?

die richtige Implementierung sieht so aus:
1
    template <typename Class>
2
    int release( Class* this_ ) noexcept
3
    {
4
        int counter = --m_counter;
5
        if( 0 == counter )
6
        {
7
            delete this_;
8
        }
9
        return counter;
10
    }

zwischen der lokalen counter variable und dem if
kann nichts passieren das zu einem falschen Ergebnis führt
Nur wenn es jemand schafft von 2 Threads aus das Objekt mit einem 
invaliden m_counter=1 in einem release und add_ref Kampf zu verwickeln - 
die add_ref und release Schnittstelle wird aber unter einem 
Safe-Interface versteckt damit das eben nicht passieren kann - keiner 
ruft add_ref/release gezielt auf
und der Counter ist eine (alte) Debug-Hilfe für triviale non-Thread 
Szenarien

in Windows-COM ist das auch nicht besser gelöst und ich brauche eben 
dieses Feature-Set - aus vielen Gründe die für 98% meiner normalen 
Entwicklung völlig unrelevant sind (DLLs, Release/Debug Mischung, 
Kompiler-Hersteller/Versions Unabhängikkeit)

Beitrag #6401031 wurde von einem Moderator gelöscht.
von cppbert (Gast)


Lesenswert?

Dennis F. schrieb im Beitrag #6401031:
> C++ und dll Schnittstellen sind generell eine schrottige Idee und zum
> scheitern verurteilt, sofern du nicht immer mit den exakt gleichen
> Compilern und Parametern arbeitest. Das ist die Pest. Hat bisher noch
> niemand zufriedenstellend gelöst.

oder du baust eine Windows-COM artiges Interface, dann geht es - aber so 
elegant wie purer C++ Code wird das niemals

von Sven B. (scummos)


Lesenswert?

cppbert schrieb:
> Dennis F. schrieb:
>> C++ und dll Schnittstellen sind generell eine schrottige Idee und zum
>> scheitern verurteilt, sofern du nicht immer mit den exakt gleichen
>> Compilern und Parametern arbeitest. Das ist die Pest. Hat bisher noch
>> niemand zufriedenstellend gelöst.
>
> oder du baust eine Windows-COM artiges Interface, dann geht es - aber so
> elegant wie purer C++ Code wird das niemals

Naja, die typische Lösung dafür ist ja eine C-Schnittstelle um das C++ 
herum ...

von cppbert (Gast)


Lesenswert?

Sven B. schrieb:
> Naja, die typische Lösung dafür ist ja eine C-Schnittstelle um das C++
> herum ...

mache ich sonst auch eher so - passt nur nicht zu der Anforderung - 
Komponenten-System das mit verschiedenen Sprachen implementiert werden 
kann und eine dynamische Kopplung von veröffentlichen 
Klassen(Object)/Properties/Methoden Aufrufen erlaubt - so eine Art 
Programmiersystem

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.