2016-01-28 5 views

Antwort

6

Nein gibt es keine atomaren Standardfunktionen für std::unique_ptr.

habe ich für ein Argument finden, warum nicht in Atomic Smart Pointers(N4058) von Herb Sutter

Lawrence Crowl hinzufügen reagierte auf:

Einer der Gründe, die Verriegelungs Shared_ptr ist so, wie es ist, eine Situation zu vermeiden in der wir die Vorbedingung auf dem atomaren Template-Parameter schwächen, dass es trivial ist und somit kein Deadlock-Risiko besteht.

Das heißt, wir könnten die Anforderung schwächen, so dass der Argumenttyp nur lockfrei sein muss, oder vielleicht nur nicht rekursiv sperren.

Während trivial für einigermaßen testbare Merkmale sorgt, sehe ich jedoch keinen wirksamen Mechanismus, um auf die schwächere Eigenschaft zu testen.

Dieser Vorschlag wurde der Nebengruppe "Nebenläufigkeit" zugewiesen und ist noch nicht verfügbar. Sie können den Status unter JTC1/SC22/WG21 - Papers 2014 Mailing2014-07

+2

Den Bezug Grund, warum (pre C++ 17) gibt 'std :: Atom ' nicht mit Smart-Pointer arbeiten, aber nicht, warum gibt es keine Überlastungen von z.B. 'atomic_compare_exchange_weak' für std :: unique_ptr, während es für std :: shared_ptr – MikeMB

+1

In der Zwischenzeit wurde N4058 als N4162 überarbeitet. Sie finden es unter den obigen Links in mailing2014-10. – Jan

4

Seien Sie vorsichtig, teilen eine veränderbare unique_ptr zwischen Threads selten sinnvoll, auch wenn der Zeiger selbst atomar war. Wenn sich sein Inhalt ändert, wie können andere Threads davon erfahren? Sie können nicht.

Betrachten Sie dieses Beispiel:

unique_ptr<MyObject> p(new MyObject); 

// Thread A 
auto ptr = p.get(); 
if (ptr) { 
    ptr->do_something(); 
} 

// Thread B 
p.reset(); 

Wie nach dem Aufruf p.get() A vermeiden, mit einem baumelnden Zeiger Thread kann?

Wenn Sie ein Objekt zwischen Threads teilen möchten, verwenden Sie shared_ptr, das genau für diesen Zweck Referenzzählung hat.


Wenn Sie wirklich es wollte, können Sie immer Ihre eigene Rolle atomic_unique_ptr, etwas entlang der Linien (vereinfacht):

#pragma once 
#include <atomic> 
#include <memory> 

template<class T> 
class atomic_unique_ptr 
{ 
    using pointer = T *; 
    std::atomic<pointer> ptr; 
public: 
    constexpr atomic_unique_ptr() noexcept : ptr() {} 
    explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {} 
    atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {} 
    atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; } 
    atomic_unique_ptr(std::unique_ptr<T>&& p) noexcept : ptr(p.release()) {} 
    atomic_unique_ptr& operator=(std::unique_ptr<T>&& p) noexcept { reset(p.release()); return *this; } 

    void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete old; } 
    operator pointer() const { return ptr; } 
    pointer operator->() const { return ptr; } 
    pointer get() const { return ptr; } 
    explicit operator bool() const { return ptr != pointer(); } 
    pointer release() { return ptr.exchange(pointer()); } 
    ~atomic_unique_ptr() { reset(); } 
}; 

template<class T> 
class atomic_unique_ptr<T[]> // for array types 
{ 
    using pointer = T *; 
    std::atomic<pointer> ptr; 
public: 
    constexpr atomic_unique_ptr() noexcept : ptr() {} 
    explicit atomic_unique_ptr(pointer p) noexcept : ptr(p) {} 
    atomic_unique_ptr(atomic_unique_ptr&& p) noexcept : ptr(p.release()) {} 
    atomic_unique_ptr& operator=(atomic_unique_ptr&& p) noexcept { reset(p.release()); return *this; } 
    atomic_unique_ptr(std::unique_ptr<T>&& p) noexcept : ptr(p.release()) {} 
    atomic_unique_ptr& operator=(std::unique_ptr<T>&& p) noexcept { reset(p.release()); return *this; } 

    void reset(pointer p = pointer()) { auto old = ptr.exchange(p); if (old) delete[] old; } 
    operator pointer() const { return ptr; } 
    pointer operator->() const { return ptr; } 
    pointer get() const { return ptr; } 
    explicit operator bool() const { return ptr != pointer(); } 
    pointer release() { return ptr.exchange(pointer()); } 
    ~atomic_unique_ptr() { reset(); } 
}; 

NB: Der Code in diesem Beitrag nicht vorgesehen hierdurch in freigesetzt wird Öffentliche Domäne

+6

Es würde eigentlich Sinn machen: Es ist eigentlich ein gemeinsames lockfree Muster, um ein Objekt auf dem Heap zu erstellen und - sobald Sie damit fertig sind - einen Zeiger darauf in einer gemeinsamen Variablen zu speichern. Wenn nun ein Thread auf das Objekt zugreifen möchte, muss er zuerst diesen Zeiger mit einem nullptr atomar austauschen und den Zeiger zurückspeichern, sobald dieser Thread beendet ist. So stellen Sie sicher, dass immer nur ein Thread gleichzeitig auf ein Objekt zugreifen kann. – MikeMB

+0

Warum verkomplizieren Sie Ihr Leben so? Überlassen Sie die Lebenszyklusverwaltung dem äußeren Objekt, das alle Worker-Threads überlebt, und verwenden Sie einen anderen *, atomaren unverarbeiteten Zeiger, der zwischen Threads geteilt wird. – rustyx

+0

ZB weil man ein Produzent ist und einer ein Verbraucher ist (oder Sie sogar mehrere Verbraucher haben). Dann MÖCHTEN Sie das Eigentum auf denjenigen übertragen, der den Zeiger bekommt. In jedem Fall: Angenommen, Sie haben eine atomare unique_ptr, dann sehe ich nicht, wie es Dinge vereinfacht, wenn Sie die Lebensdauer getrennt von Sichtbarkeit/Zugänglichkeit behandeln. – MikeMB

4

Der Grund, dass es möglich ist, eine atomare Instanz von std::shared_ptr bereitzustellen, und es ist nicht möglich, dies für std::unique_ptr zu tun, ist in ihrer Signatur angedeutet. Vergleichen:

  • std::shared_ptr<T> vs
  • std::unique_ptr<T, D> wo D der Typ des Deleter ist.

std::shared_ptr braucht einen Steuerblock, wo die starke und schwache Zahl gehalten werden, so zuzuteilen typen Löschen des Deleter kam zu einem trivial Kosten (a einfach etwas größerer Steuerblock).

Als Ergebnis ist das Layout von std::shared_ptr<T> im allgemeinen ähnlich:

template <typename T> 
struct shared_ptr { 
    T* _M_ptr; 
    SomeCounterClass<T>* _M_counters; 
}; 

Und es ist möglich, atomar, um den Austausch dieser beiden Zeiger ausführen.


std::unique_ptr hat eine Null-Overhead-Richtlinie; die Verwendung eines std::unique_ptr sollte keinen Overhead im Vergleich zur Verwendung eines rohen Zeigers verursachen.

Als Ergebnis ist das Layout von std::unique_ptr<T, D> im allgemeinen ähnlich:

template <typename T, typename D = default_delete<T>> 
struct unique_ptr { 
    tuple<T*, D> _M_t; 
}; 

Wo die tuple EBO verwendet (Leer Basis-Optimierung), so dass, wenn D Null-Größe dann sizeof(unique_ptr<T>) == sizeof(T*).

jedoch in den Fällen, in denen D NICHT Größe Null ist, kocht die Umsetzung bis:

template <typename T, typename D = default_delete<T>> 
struct unique_ptr { 
    T* _M_ptr; 
    D _M_del; 
}; 

Dieser D ist der Kicker hier; Es ist im Allgemeinen nicht möglich, zu garantieren, dass D auf atomare Weise ausgetauscht werden kann, ohne auf Mutexe angewiesen zu sein.

Daher ist es nicht möglich, eine std::atomic_compare_exchange* Suite von spezialisierten Routine für die generische bereitzustellen.

Beachten Sie, dass die Standard-Garantie nicht einmal, dass sizeof(unique_ptr<T>) == sizeof(T*) AFAIK, obwohl es sich um eine gemeinsame Optimierung ist.