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<typenameClass>
5
classRefCounter
6
{
7
private:
8
std::atomic<int>m_counter{1};
9
public:
10
intadd_ref()noexcept
11
{
12
return++m_counter;
13
}
14
15
intrelease(Class*this_)noexcept
16
{
17
assert(m_counter>0);
18
if(--m_counter==0)
19
{
20
deletethis_;
21
}
22
returnm_counter;
23
}
24
};
25
26
#define MY_API // DLL import/export
27
28
classIRefCounted
29
{
30
virtualintMY_APIadd_ref()=0;
31
virtualintMY_APIrelease()=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); } \
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.
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
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...
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?
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).
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
cppbert schrieb:> Ueber andere Interfaces die in diesem Beispiel fehlen
Wie übergibst du diese Objekte, dass du über verschiedene Interfaces
darauf zugreifen kannst?
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
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
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.
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
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...
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
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.
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
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)
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
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 ...
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