Ich habe diesen Code, der nicht funktioniert, aber ich denke, die Absicht ist klar:
testmakeshared.cpp
#include <memory>
class A {
public:
static ::std::shared_ptr<A> create() {
return ::std::make_shared<A>();
}
protected:
A() {}
A(const A &) = delete;
const A &operator =(const A &) = delete;
};
::std::shared_ptr<A> foo()
{
return A::create();
}
Aber ich bekomme diesen Fehler, wenn ich es kompiliere:
g++ -std=c++0x -march=native -mtune=native -O3 -Wall testmakeshared.cpp
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:52:0,
from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/memory:86,
from testmakeshared.cpp:1:
testmakeshared.cpp: In constructor ‘std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc) [with _Tp = A, _Alloc = std::allocator<A>, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’:
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:518:8: instantiated from ‘std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:986:35: instantiated from ‘std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:313:64: instantiated from ‘std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:531:39: instantiated from ‘std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:547:42: instantiated from ‘std::shared_ptr<_Tp1> std::make_shared(_Args&& ...) [with _Tp = A, _Args = {}]’
testmakeshared.cpp:6:40: instantiated from here
testmakeshared.cpp:10:8: error: ‘A::A()’ is protected
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:400:2: error: within this context
Compilation exited abnormally with code 1 at Tue Nov 15 07:32:58
Diese Nachricht besagt im Grunde, dass eine zufällige Methode weit unten im Vorlageninstanziierungsstapel von ::std::make_shared
nicht auf den Konstruktor zugreifen kann, weil er geschützt ist.
Aber ich möchte wirklich beides verwenden ::std::make_shared
und verhindern, dass jemand ein Objekt dieser Klasse erstellt, auf das a nicht zeigt ::std::shared_ptr
. Gibt es eine Möglichkeit, dies zu erreichen?
c++
c++11
shared-ptr
Allgegenwärtig
quelle
quelle
Antworten:
Diese Antwort ist wahrscheinlich besser und die, die ich wahrscheinlich akzeptieren werde. Aber ich habe mir auch eine Methode ausgedacht, die hässlicher ist, aber trotzdem alles inline lässt und keine abgeleitete Klasse erfordert:
Edit 2017-01-06: Ich habe dies geändert, um deutlich zu machen, dass diese Idee klar und einfach auf Konstruktoren erweiterbar ist, die Argumente annehmen, weil andere Leute Antworten in diese Richtung gaben und diesbezüglich verwirrt zu sein schienen.
quelle
protected
stattprivate
. Und mit "es" beziehe ich mich auf diethis_is_private
Klasse, die in einem solchen Fall vielleicht umbenannt werden sollte. Normalerweise nenne ich esconstructor_access
in meinem Code.{}
das private Tag übergeben, ohne Zugriff auf den Typnamen zu haben (getestet mit g ++ 4.9.0). Ohne echte Parameter versucht es,A
aus {} zu konstruieren , obwohl ich keine Ahnung habe, warum und schlägt fehl. Ich denke, den Konstruktor this_is_private privat zu machen und eine statische Methode zum Erstellen bereitzustellen, behebt ihn, da es keine Möglichkeit geben sollte, von außen auf diese Methode zuzugreifen, es sei denn, Sie verlieren den Typ in einer Signatur einer Mitgliedsfunktion.this_is_private
einen privaten Ctor können Sie Klasse A ein Freund machen. Scheint die Lücke zu schließen.Betrachten der Anforderungen für die
std::make_shared
Erstellung von shared_ptr in 20.7.2.2.6 [util.smartptr.shared.create], Absatz 1:Da die Anforderung in Bezug auf diesen Ausdruck unbedingt festgelegt ist und Dinge wie der Umfang nicht berücksichtigt werden, denke ich, dass Tricks wie Freundschaft richtig sind.
Eine einfache Lösung besteht darin, daraus abzuleiten
A
. Dies muss keineA
Schnittstelle oder sogar einen polymorphen Typ erfordern .quelle
shared_ptr
zum Zeitpunkt der Instanziierung ein Deleter gespeichert wird und wenn Siemake_shared
den Deleter verwenden, muss unbedingt der richtige Typ verwendet werden.Möglicherweise die einfachste Lösung. Basierend auf der vorherigen Antwort von Mohit Aron und unter Einbeziehung des Vorschlags von dlf.
quelle
A
es nicht standardmäßige Konstruktoren gibt, müssen Sie diese auch verfügbar machen :struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };
. Dies macht alle privaten Konstruktoren vonA
alsmake_shared_enabler
Konstruktoren sichtbar . Die Verwendung der Konstruktor-Vererbungsfunktion (using A::A;
) scheint hier nicht zu helfen, da Konstruktoren weiterhin privat sind.class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }
. Siehe hier cpp.sh/65qbr .Hier ist eine gute Lösung dafür:
quelle
MakeSharedEnabler
lokal im Inneren definiert wirdA::Create()
.quelle
Wie wäre es damit?
quelle
::std::make_shared
aber Funktionen, die über das einfache Erstellen eines shared_ptr für etwas hinausgehen. Es ordnet den Referenzzähler zusammen mit dem Objekt so zu, dass sie nahe beieinander liegen. Ich möchte wirklich, wirklich verwenden::std::make_shared
.Da mir die bereits bereitgestellten Antworten nicht gefallen haben, habe ich mich entschlossen, nach einer Lösung zu suchen, die nicht so allgemein ist wie die vorherigen Antworten, aber mir gefällt sie besser (tm). Rückblickend ist es nicht viel schöner als das von Omnifarius, aber es könnte auch andere Leute geben, die es mögen :)
Dies ist nicht von mir erfunden, aber es ist die Idee von Jonathan Wakely (GCC-Entwickler).
Leider funktioniert es nicht mit allen Compilern, da es auf einer kleinen Änderung in der Implementierung von std :: allocate_shared beruht. Diese Änderung ist jetzt ein vorgeschlagenes Update für die Standardbibliotheken, sodass sie möglicherweise in Zukunft von allen Compilern unterstützt wird. Es funktioniert mit GCC 4.7.
Die Änderungsanforderung für die C ++ - Standardbibliotheksarbeitsgruppe ist hier: http://lwg.github.com/issues/lwg-active.html#2070
Der GCC-Patch mit einer Beispielverwendung ist hier: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html
Die Lösung basiert auf der Idee, std :: allocate_shared (anstelle von std :: make_shared) mit einem benutzerdefinierten Allokator zu verwenden, der mit dem privaten Konstruktor als Freund der Klasse deklariert wird.
Das Beispiel aus dem OP würde folgendermaßen aussehen:
Ein komplexeres Beispiel, das auf dem Dienstprogramm basiert, an dem ich arbeite. Damit konnte ich Lucs Lösung nicht gebrauchen. Aber der von Omnifarius könnte angepasst werden. Nicht, dass im vorherigen Beispiel jeder mit dem MyAlloc ein A-Objekt erstellen kann. In diesem Fall gibt es außer der create () -Methode keine Möglichkeit, A oder B zu erstellen.
quelle
Idealerweise würde die perfekte Lösung Ergänzungen zum C ++ - Standard erfordern. Andrew Schepler schlägt Folgendes vor:
( Hier geht es zum ganzen Thread)
Verwendung
Wenn / wenn das Obige zum Standard hinzugefügt wird, würden wir einfach tun:
Wenn dies für Sie auch nach einer wichtigen Ergänzung des Standards klingt, können Sie der verknüpften isocpp-Google-Gruppe Ihre 2 Cent hinzufügen.
quelle
Mir ist klar, dass dieser Thread ziemlich alt ist, aber ich habe eine Antwort gefunden, die keine Vererbung oder zusätzliche Argumente für den Konstruktor erfordert, die ich anderswo nicht sehen konnte. Es ist jedoch nicht tragbar:
Ich habe unter Windows und Linux getestet, es muss möglicherweise für verschiedene Plattformen angepasst werden.
quelle
std::shared_ptr_access
des Standards, der als einfach und tragbar angesehen werden kann.Es gibt ein haarigeres und interessanteres Problem, das auftritt, wenn zwei eng verwandte Klassen A und B zusammenarbeiten.
Sagen wir, A ist die "Meisterklasse" und B ihr "Sklave". Wenn Sie die Instanziierung von B nur auf A beschränken möchten, machen Sie den Konstruktor von B privat und Freund B auf A wie folgt
Leider bringt das Aufrufen
std::make_shared<B>()
von einer Methode vonA
den Compiler dazu, sich zu beschwerenB::B()
zu , privat zu sein.Meine Lösung hierfür besteht darin, eine öffentliche
Pass
Dummy-Klasse (genau wienullptr_t
) im Inneren zu erstellen, die einenB
privaten Konstruktor hat und mit dem Konstruktor befreundet istA
und ihnB
öffentlich macht undPass
seine Argumente wie folgt ergänzt .quelle
Wenn Sie auch einen Konstruktor aktivieren möchten, der Argumente akzeptiert, kann dies etwas hilfreich sein.
quelle
[Bearbeiten] Ich habe den oben genannten Thread zu einem standardisierten
std::shared_ptr_access<>
Vorschlag gelesen . Darin befand sich eine Antwort, in der ein Fixstd::allocate_shared<>
und ein Beispiel für seine Verwendung vermerkt waren. Ich habe es unten an eine Factory-Vorlage angepasst und unter gcc C ++ 11/14/17 getestet. Es funktioniert auch mitstd::enable_shared_from_this<>
, wäre also meiner ursprünglichen Lösung in dieser Antwort offensichtlich vorzuziehen. Hier ist es...[Orig] Ich habe eine Lösung mit dem Aliasing-Konstruktor für gemeinsam genutzte Zeiger gefunden. Es ermöglicht sowohl ctor als auch dtor, privat zu sein, sowie die Verwendung des endgültigen Spezifizierers.
Beachten Sie, dass der obige Ansatz nicht gut funktioniert,
std::enable_shared_from_this<>
da sich die Initialestd::shared_ptr<>
auf den Wrapper und nicht auf den Typ selbst bezieht . Wir können dies mit einer äquivalenten Klasse beheben, die mit der Fabrik kompatibel ist ...Schließlich sagte jemand, Clang habe sich darüber beschwert, dass Factory :: Type als Freund privat sei. Machen Sie es also einfach öffentlich, wenn dies der Fall ist. Das Aussetzen schadet nicht.
quelle
Ich hatte das gleiche Problem, aber keine der vorhandenen Antworten war wirklich zufriedenstellend, da ich Argumente an den geschützten Konstruktor übergeben muss. Außerdem muss ich dies für mehrere Klassen tun, die jeweils unterschiedliche Argumente verwenden.
Zu diesem Zweck und auf der Grundlage einiger der vorhandenen Antworten, die alle ähnliche Methoden verwenden, präsentiere ich dieses kleine Nugget:
quelle
Die Wurzel des Problems liegt darin, dass die Funktion oder Klasse, die Ihr Freund Ihrem Konstruktor auf niedrigerer Ebene aufruft, ebenfalls befreundet sein muss. std :: make_shared ist nicht die Funktion, die Ihren Konstruktor tatsächlich aufruft, so dass eine Freundschaft keinen Unterschied macht.
std :: _ Ref_count_obj ruft Ihren Konstruktor tatsächlich auf, daher muss er ein Freund sein. Da das etwas dunkel ist, verwende ich ein Makro
Dann sieht Ihre Klassendeklaration ziemlich einfach aus. Sie können ein einzelnes Makro zum Deklarieren des ptr und der Klasse erstellen, wenn Sie dies bevorzugen.
Dies ist eigentlich ein wichtiges Thema. Um wartbaren, portablen Code zu erstellen, müssen Sie so viel wie möglich von der Implementierung ausblenden.
Wenn Sie nicht wissen, wie Sie mit Ihrem Smart Pointer umgehen, müssen Sie unbedingt Ihr typedef verwenden. Wenn Sie jedoch immer eine mit make_shared erstellen müssen, wird der Zweck zunichte gemacht.
Das obige Beispiel erzwingt, dass Code, der Ihre Klasse verwendet, Ihren Smart Pointer-Konstruktor verwendet. Wenn Sie also zu einer neuen Variante von Smart Pointer wechseln, ändern Sie Ihre Klassendeklaration und haben eine gute Chance, fertig zu werden. Gehen Sie NICHT davon aus, dass Ihr nächster Chef oder Ihr nächstes Projekt STL, Boost usw. verwenden wird, um es eines Tages zu ändern.
Seit fast 30 Jahren habe ich einen hohen Preis für Zeit, Schmerzen und Nebenwirkungen gezahlt, um dies zu reparieren, als es vor Jahren falsch gemacht wurde.
quelle
std::_Ref_count_obj
ist ein Implementierungsdetail. Das bedeutet, dass diese Lösung möglicherweise vorerst auf Ihrer Plattform für Sie funktioniert. Aber es funktioniert möglicherweise nicht für andere und funktioniert möglicherweise nicht mehr, wenn Ihr Compiler aktualisiert wird oder wenn Sie nur die Kompilierungsflags ändern.Sie können dies verwenden:
quelle
std::make_shared
.quelle