Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

18.07.2012, 17:56

Einen Pointer auf Pointer mit std::unique_ptr verwenden?

Ich versuche mich grade etwas in std::unique_ptr einzuarbeiten dazu wollte ich eine einfache DX11 Anwendung mit unique_ptr statt den normalen Pointern schreiben aber mir kommt es so vor als wenn das DX Interface kontraproduktiv zu den unique_ptr arbeitet. Hier nochmal ein Kleiner Codeausschnitt:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool setupDirectX( HWND hWnd )
{
    HRESULT result;
    IDXGIFactory * factory; // Hier noch den normalen ptr benutzt

    result = CreateDXGIFactory( __uuidof( IDXGIFactory ), (void**)&factory );
    if(!result)
        return false;

    std::unique_ptr<IDXGIAdapter> Adapter;
    IDXGIAdapter * pAdapter = Adapter.get(); // Kann man diese Zeile nicht rauslassen?
    result = factory->EnumAdapters( 0, &pAdapter );
        //...
}


Ist es Sinn der Sache noch einen zusätzlichen Pointer deklarieren zu müssen damit ich einen **IDXGIAdpater bekomme oder kann man direkt von dem unique_ptr einen **IDXGIAdpater erhalten. Vielleicht habe ich aber aber auch die richtige Benutzung von uniqe_ptr noch nicht richtig verstanden, wenn ich ein struct A habe und einen *A mit unique_ptr verwenden möchte deklariere ich mir dann einen std::unique_ptr<A> oder einen std::unique_ptr<A*>?
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

eXpl0it3r

Treue Seele

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

2

18.07.2012, 18:19

Windows und DX API nutzen ihre eigenen Datentypen und sind überhaupt nicht für SmartPointers ausgelegt, es ist darum ein bisschen fragwürdig diese zu nutzen, ausserdem macht es nur Sinn SmartPointers zu nutzen wenn diese auch die effektiven Besitzer des Objektes sind, was ja mit der Factory nicht der Fall ist.
Der Sinn von SmartPointer ist das manuelle Aufrufen von delete zu verhindern. DX nimmt dir hier die Arbeit bereits ab.

In deim Beispiel verlangt die Funktion ein raw pointer und nicht ein SmartPointer darum funktioniert das nicht (und macht nicht viel Sinn...).

Du solltest nocheinmal lesen für was die SP gut sind. ;)
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

3

18.07.2012, 18:35

COM Objekte werden nicht per new und delete erzeugt, daher kannst du solche Pointer nicht einfach so in einen std::unique_ptr stecken. Aber du kannst z.B. einen eigenen Smartpointer verwenden:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
template <class T>
class com_ptr
{
private:
  T* ptr;

  static void release(T* ptr)
  {
    if (ptr)
      ptr->Release();
  }

  static void acquire(T* ptr)
  {
    if (ptr)
      ptr->AddRef();
  }

public:
  com_ptr(T* ptr = nullptr)
    : ptr(ptr)
  {
  }

  com_ptr(const com_ptr& p)
    : ptr(p.ptr)
  {
    acquire(ptr);
  }

  com_ptr(com_ptr&& p)
    : ptr(p.ptr)
  {
    p.ptr = nullptr;
  }

  ~com_ptr()
  {
    release(ptr);
  }

  com_ptr& operator =(const com_ptr& p)
  {
    acquire(p.ptr);
    release(ptr);
    ptr = p.ptr;
    return *this;
  }

  com_ptr& operator =(com_ptr&& p)
  {
    swap(*this, p);
    return *this;
  }

  void release()
  {
    release(ptr);
    ptr = nullptr;
  }

  T* operator ->() const { return ptr; }

  operator T*() const { return ptr; }

  friend void swap(com_ptr& a, com_ptr& b)
  {
    using std::swap;
    swap(a.ptr, b.ptr);
  }
};


Oder du gibst deinem std::unique_ptr einen entsprechenden deleter, der eben Release() aufruft...

CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

4

18.07.2012, 19:26

Noch zu deinem Beispiel, weil du darin einen groben Fehler hast, den du unbedingt verstehen musst: Zwar kompiliert der Code, weil er syntaktisch richtig ist, er tut aber nicht im geringsten das, was du hier erwartest.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
// Legt einen neuen unique_ptr an, der mit nullptr (oder vor C++11 einfach 0, auch bekannt als NULL) initialisiert wird.
std::unique_ptr<IDXGIAdapter> Adapter;
// Legt einen neuen rohen Zeiger an, der mit dem Zeiger aus Adapter, also ebenfalls nullptr, initialisiert wird.
IDXGIAdapter * pAdapter = Adapter.get();
// Lässt pAdapter auf den zurückgegebenen Adapter zeigen, NICHT jedoch Adapter
result = factory->EnumAdapters( 0, &pAdapter );
// Nur pAdapter zeigt auf den zurückgegebenen Adapter
// Adapter ist dagegen weiterhin nullptr, deshalb wird am Ende NICHTS freigegeben


Wie dot sagt, ist das nicht das einzige Problem. unique_ptr ist für COM-ähnliche Objekte denkbar ungeeignet. Denn hättest du es geschafft, tatsächlich den unique_ptr auf das zurückgegebene Adapter-Objekt zeigen zu lassen, dann würde unique_ptr bei Zerstörung für das Adapter-Objekt einfach delete aufrufen. Das wäre mindestens genauso falsch, denn COM-Objekte müssen per Release() freigegeben werden.

Zwar lässt sich unique_ptr über Spezialisierung oder die explizite Angabe eines Deleters an spezielle Objekttypen anpassen, für eine so große Vielfalt von COM-Objekten, wie sie die DirectX-API anbietet, wäre das jedoch äußerst mühsam und fehleranfällig.

dots Klasse kannst du um eine Methode rebind() erweitern:

C-/C++-Quelltext

1
2
3
4
5
T** rebind()
{
   release();
   return &ptr;
}


Damit erreichst du das, wonach du im Eingangspost gefragt hast:

C-/C++-Quelltext

1
2
com_ptr<IDXGIAdapter> Adapter;
result = factory->EnumAdapters( 0, Adapter.rebind() );


Windows und DX API nutzen ihre eigenen Datentypen und sind überhaupt nicht für SmartPointers ausgelegt, es ist darum ein bisschen fragwürdig diese zu nutzen, ausserdem macht es nur Sinn SmartPointers zu nutzen wenn diese auch die effektiven Besitzer des Objektes sind, was ja mit der Factory nicht der Fall ist.
Der Sinn von SmartPointer ist das manuelle Aufrufen von delete zu verhindern. DX nimmt dir hier die Arbeit bereits ab.

Nein, DX nimmt dir die Arbeit in keinster Weise ab. Genauso wie du ohne Smart Pointers für Freispeicherobjekte manuell delete aufrufen musst, musst du für COM-Objekte manuell Release aufrufen.
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

5

18.07.2012, 19:49

Ok ich hab mir schon gedacht das smartpointer mit den DX Typen nicht harmonieren da sie ja auch IUnknown erben. Aber die letzte Frage in meinem Post besteht immernoch, wenn der smartpointer zerstört wird wie sieht dann der delete Aufruf aus? Bei std:unique_ptr<A> wäre doch das gleiche wie...

C-/C++-Quelltext

1
2
int A = 0;
delete A;


oder? Richtig müsste man doch immer einen std::unique_ptr<A*> verwenden?
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

eXpl0it3r

Treue Seele

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

6

18.07.2012, 20:01

Wie bereits erwähnt ist der SmartPointer der Besitzer des Objektes, darum musst du dich auch nicht um irgendwelche delets kümmern. Es wär somit unique_ptr<A>.
Mit C++11 sollte man verwenden:

C-/C++-Quelltext

1
std:.unique_ptr<T> hello = std:.make_unique(T);

Da dies aber mit VS10 noch nicht funktioniert muss man halt noch:

C-/C++-Quelltext

1
std::unique_ptr<T> hello(new T);
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

7

18.07.2012, 20:04

Ok ich hab mir schon gedacht das smartpointer mit den DX Typen nicht harmonieren da sie ja auch IUnknown erben.

Die Standard-Smart-Pointer harmonieren nicht damit, angepasste Smart Pointer dagegen sehr gut. Leider ist das Schreiben von eigenen Smart-Pointern alles andere als trivial. dots Klasse sollte im Regelfall keine Probleme machen; es gibt jedoch einige Fälle, in denen mit der impliziten Konvertierung unerwartetes Verhalten auftreten kann. Die STL geht hier einen etwas strikteren Weg, der solche Fälle vermeidet, indem implizite Konvertierung einfach verboten wird.

Aber die letzte Frage in meinem Post besteht immernoch, wenn der smartpointer zerstört wird wie sieht dann der delete Aufruf aus? Bei std:unique_ptr<A> wäre doch das gleiche wie...

C-/C++-Quelltext

1
2
int A = 0;
delete A;


oder? Richtig müsste man doch immer einen std::unique_ptr<A*> verwenden?

Nein, das würde ja überhaupt keinen Sinn ergeben. unique_ptr<A> speichert einen Zeiger auf ein A-Objekt und ruft bei Zerstörung delete mit diesem Zeiger auf. unique_ptr<A*> würde keine A-Objekte mehr verwalten, sondern Zeiger auf A-Objekte, intern besäße es also schon einen Doppelzeiger.
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

8

18.07.2012, 22:01

dots Klasse sollte im Regelfall keine Probleme machen; es gibt jedoch einige Fälle, in denen mit der impliziten Konvertierung unerwartetes Verhalten auftreten kann.

Jap, ich möchte nur meine com_ptr gern direkt wie normale Pointer an entsprechende Methoden übergeben und finde, dass in dem Fall die Vorteile bei weitem überwiegen. Die Anzahl der potentiellen Probleme ist hier wohl relativ gering, da ich im konkreten Fall von com_ptr ja eigentlich will, dass das Objekt sich mehr oder weniger in jedem Kontext wie ein normaler Pointer verhält. Aber im Allgemeinen ist bei benutzerdefinierten Konvertierungen Vorsicht geboten, da hast du absolut recht.

dots Klasse kannst du um eine Methode rebind() erweitern: [...]

Jap, das ist eine Möglichkeit, wobei ich persönlich diese Form der "Initialisierung" generell nicht sehr schön finde (vor allem die wohl immer noch recht verbreitete Lösung über einen operator & find ich sehr hässlich, auch wenn ich's mangels besserer Ideen früher auch so gemacht hab; deine Lösung mit rebind() ist schon ziemlich elegant). Ich bin mittlerweile dazu übergegangen, com_ptr nurmehr über den Konstruktor zu initialisieren. Entweder indem ich konsequent entsprechende Factoryfunktionen benutze (üblicherweise intern in einem unnamed namespace definiert), oder inline über Lambdas:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  com_ptr<ID3D11Buffer> vb = [](ID3D11Device* device, UINT buffer_size) -> ID3D11Buffer*
  {
    D3D11_BUFFER_DESC buffer_desc;
    buffer_desc.ByteWidth = buffer_size;
    buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
    buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    buffer_desc.MiscFlags = 0;
    buffer_desc.StructureByteStride = 0;

    ID3D11Buffer* buffer;
    D3D11::checkError(device->CreateBuffer(&buffer_desc, nullptr, &buffer));

    return buffer;
  }(device, buffer_size);


Man könnte alternativ überhaupt das com_ptr template um einen entsprechenden Konstruktor erweitern:

C-/C++-Quelltext

1
2
3
4
5
  template <typename F>
  com_ptr(F f)  // explicit?
  {
    f(&ptr);
  }


womit sich folgende Syntax ergibt

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
  com_ptr<ID3D11Buffer> vb([=](ID3D11Buffer** buffer)
  {
    D3D11_BUFFER_DESC buffer_desc;
    buffer_desc.ByteWidth = buffer_size;
    buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
    buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    buffer_desc.MiscFlags = 0;
    buffer_desc.StructureByteStride = 0;

    D3D11::checkError(device->CreateBuffer(&buffer_desc, nullptr, buffer));
  });

Dieser Beitrag wurde bereits 16 mal editiert, zuletzt von »dot« (18.07.2012, 23:05)


CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

9

19.07.2012, 10:47

Hehe, das ist mir dann aber doch zu umständlich, ich bin mit rebind sehr zufrieden. Überladene &-Operatoren kann ich auch nicht ausstehen.

Zitat von »"dot"«

Jap, ich möchte nur meine com_ptr gern direkt wie normale Pointer an entsprechende Methoden übergeben und finde, dass in dem Fall die Vorteile bei weitem überwiegen.

Sehe ich genauso, ich nutze aber generell eine andere Referenzsemantik als die STL. Bei mir akquirieren einfach alle Standardkonstruktionen (auch von rohen Zeigern) neue Referenzen. Binden bestehender Referenzen geht dagegen über explizites bind_com(...), was einem typdeduzierten com_ptr<...>::bind bzw. com_ptr<...>(..., bind_reference) entspricht. Um den genannten Fällen zu entkommen, in denen implizite Casts gefährlich werden können, hat mein com_ptr zudem zwei Modi, "normal" und "critical". Erzeugende Funktionen geben nur com_ptrs im "critical"-Modus zurück, welcher implizite Casts zu rohen Zeigern verbietet. Der Standardmodus ist dagegen "normal", und erlaubt auch implizite Casts zu rohen Zeigern. com_ptr verwende ich aber ohnehin nur für Besitznahme, im normalen Objektgebrauch einfach rohe Zeiger.
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »CodingCat« (19.07.2012, 10:55)


10

19.07.2012, 15:07

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  com_ptr<ID3D11Buffer> vb = [](ID3D11Device* device, UINT buffer_size) -> ID3D11Buffer*
  {
    D3D11_BUFFER_DESC buffer_desc;
    buffer_desc.ByteWidth = buffer_size;
    buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
    buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    buffer_desc.MiscFlags = 0;
    buffer_desc.StructureByteStride = 0;

    ID3D11Buffer* buffer;
    D3D11::checkError(device->CreateBuffer(&buffer_desc, nullptr, &buffer));

    return buffer;
  }(device, buffer_size);


Als ich mir den Code zum erstenmal angesehen habe hab ich kein Wort verstanden aber dann habe ich gelesen das das eine Lambda-Funktion ist. Da ich Lambda nur aus C# kenne würde ich gerne Wissen was das [] und [=] vor der Parameterliste bedeuten soll.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
  com_ptr<ID3D11Buffer> vb([=](ID3D11Buffer** buffer)
  {
    D3D11_BUFFER_DESC buffer_desc;
    buffer_desc.ByteWidth = buffer_size;
    buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
    buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    buffer_desc.MiscFlags = 0;
    buffer_desc.StructureByteStride = 0;

    D3D11::checkError(device->CreateBuffer(&buffer_desc, nullptr, buffer));
  });


Hier verstehe ich wieder nix^^

1. Die Parameterliste des Lambda möchte ein ID3D11Buffer**, wo wird der übergeben?

2. Müsste der Lambda nicht eigentlich einen return ID3D11Buffer* haben damit der com_ptr<ID3D11Buffer> vb daraus einen ID3D11Buffer** macht?
greate minds discuss ideas;
average minds discuss events;
small minds discuss people.

Werbeanzeige