Mam ten kod, który nie działa, ale myślę, że cel jest jasny:
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();
}
Ale pojawia się ten błąd, gdy go kompiluję:
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
Ten komunikat w zasadzie mówi, że jakaś metoda losowa w dół w stosie tworzenia instancji szablonu ::std::make_shared
nie może uzyskać dostępu do konstruktora, ponieważ jest chroniona.
Ale naprawdę chcę używać obu tych elementów ::std::make_shared
i uniemożliwić komukolwiek tworzenie obiektu tej klasy, na który nie wskazuje ::std::shared_ptr
. Czy jest jakiś sposób na osiągnięcie tego?
c++
c++11
shared-ptr
Wszelaki
źródło
źródło
Odpowiedzi:
Ta odpowiedź jest prawdopodobnie lepsza i ta, którą prawdopodobnie zaakceptuję. Ale wymyśliłem też metodę, która jest brzydsza, ale wciąż pozwala, aby wszystko było w linii i nie wymaga pochodnej klasy:
Edytuj 2017-01-06: Zmieniłem to, aby wyjaśnić, że ten pomysł jest wyraźnie i po prostu rozszerzalny na konstruktorów, którzy biorą argumenty, ponieważ inni ludzie udzielali odpowiedzi w ten sposób i wydawali się zdezorientowani.
źródło
protected
zamiastprivate
. I przez „to” mam na myślithis_is_private
klasę, którą w takim przypadku należy zmienić. Zwykle nazywam toconstructor_access
w moim kodzie.{}
do prywatnego tagu bez dostępu do nazwy typu (testowane g ++ 4.9.0). Bez rzeczywistych parametrów próbuje skonstruowaćA
z {}, chociaż nie mam pojęcia dlaczego, i kończy się niepowodzeniem. Myślę, że ustawienie konstruktora this_is_private na prywatny i zapewnienie statycznej metody jego utworzenia naprawia go, ponieważ nie powinno być sposobu na uzyskanie dostępu do tej metody z zewnątrz, chyba że wycieknie typ z podpisu funkcji członka.this_is_private
prywatnego doradcę, możesz uczynić z klasy A przyjaciela. Wygląda na to, żeby zamknąć lukę.Patrząc na wymagania dotyczące
std::make_shared
20.7.2.2.6 tworzenia share_ptr [util.smartptr.shared.create], akapit 1:Ponieważ wymóg jest bezwarunkowo określony w odniesieniu do tego wyrażenia, a rzeczy takie jak zakres nie są brane pod uwagę, myślę, że sztuczki takie jak przyjaźń są już dostępne.
Prostym rozwiązaniem jest czerpanie
A
. Nie musi to wymagać tworzeniaA
interfejsu ani nawet typu polimorficznego.źródło
shared_ptr
przechowuje urządzenie do usuwania w momencie tworzenia wystąpienia, a jeśli używasz urządzeniamake_shared
do usuwania, absolutnie musisz używać odpowiedniego typu.Prawdopodobnie najprostsze rozwiązanie. Na podstawie poprzedniej odpowiedzi Mohita Arona i uwzględnienia sugestii dlf.
źródło
A
ma niestandardowych konstruktorów będzie trzeba także je odsłonić:struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };
. To sprawia, że wszystkie prywatne konstruktory sąA
widoczne jakomake_shared_enabler
konstruktory.using A::A;
Wydaje się, że użycie funkcji dziedziczenia konstruktorów ( ) nie pomaga tutaj, ponieważ konstruktory będą nadal prywatne.class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }
. Zobacz tutaj cpp.sh/65qbr .Oto fajne rozwiązanie tego:
źródło
MakeSharedEnabler
lokalnie wewnątrzA::Create()
.Co powiesz na to?
źródło
::std::make_shared
funkcjonalność wykracza poza to, że po prostu robi coś wspólnego. Przydziela liczbę referencji wraz z obiektem, dzięki czemu są one umieszczone blisko siebie. Naprawdę bardzo chcę użyć::std::make_shared
.źródło
Ponieważ nie podobały mi się już podane odpowiedzi, postanowiłem poszukać i znalazłem rozwiązanie, które nie jest tak ogólne jak poprzednie odpowiedzi, ale bardziej mi się podoba (tm). Z perspektywy czasu nie jest to dużo ładniejsze niż to, które zapewnia Omnifarius, ale mogą być też inni ludzie, którzy to lubią :)
Nie zostało to wymyślone przeze mnie, ale pomysł Jonathana Wakely (programisty GCC).
Niestety nie działa ze wszystkimi kompilatorami, ponieważ polega na niewielkiej zmianie w implementacji std :: assignate_shared. Ale ta zmiana jest teraz proponowaną aktualizacją standardowych bibliotek, więc może być obsługiwana przez wszystkie kompilatory w przyszłości. Działa na GCC 4.7.
Żądanie zmiany standardowej biblioteki grupy roboczej biblioteki C ++ jest dostępne tutaj: http://lwg.github.com/issues/lwg-active.html#2070
Łatka GCC z przykładowym zastosowaniem znajduje się tutaj: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html
Rozwiązanie działa na idei użycia std :: assignate_shared (zamiast std :: make_shared) z niestandardowym alokatorem, który jest zadeklarowany jako przyjaciel klasy z prywatnym konstruktorem.
Przykład z PO wyglądałby tak:
Bardziej złożony przykład oparty na narzędziu, nad którym pracuję. Dzięki temu nie mogłem skorzystać z rozwiązania Luca. Ale ten Omnifariusa można dostosować. Nie dlatego, że podczas gdy w poprzednim przykładzie każdy może utworzyć obiekt A za pomocą MyAlloc w tym, nie ma sposobu na utworzenie A lub B oprócz metody create ().
źródło
Uważam, że idealne rozwiązanie wymagałoby uzupełnienia standardu C ++. Andrew Schepler proponuje, co następuje:
(Przejdź tutaj, aby zobaczyć cały wątek)
Stosowanie
Jeśli / kiedy powyższe zostanie dodane do standardu, po prostu zrobilibyśmy:
Jeśli wydaje ci się to również ważnym dodatkiem do standardu, dodaj 2 centy do połączonej grupy dyskusyjnej isocpp Google.
źródło
Zdaję sobie sprawę, że ten wątek jest dość stary, ale znalazłem odpowiedź, która nie wymaga dziedziczenia ani dodatkowych argumentów dla konstruktora, których nie widziałbym gdzie indziej. Nie jest to jednak przenośne:
Testowałem na Windowsie i Linuksie, może to wymagać dostosowania na różnych platformach.
źródło
std::shared_ptr_access
do standardu, co można uznać za pozwalające na wykonanie powyższego w prosty i przenośny sposób.Jest bardziej włochaty i interesujący problem, który ma miejsce, gdy masz dwie ściśle powiązane klasy A i B, które działają razem.
Powiedzmy, że A jest „klasą master”, a B „niewolnikiem”. Jeśli chcesz ograniczyć tworzenie instancji B tylko do A, ustawisz konstruktor B na prywatny, a przyjaciela B na A w ten sposób
Niestety wywołanie
std::make_shared<B>()
z metodyA
spowoduje, że kompilator narzeka naB::B()
prywatność.Moim rozwiązaniem jest utworzenie publicznej
Pass
klasy manekina (podobnie jaknullptr_t
) wewnątrz,B
która ma prywatnego konstruktora i jest przyjazna,A
i czyniB
konstruktorem publicznym i dodajePass
do swoich argumentów, takich jak ten.źródło
Jeśli chcesz również włączyć konstruktora, który pobiera argumenty, może to trochę pomóc.
źródło
[Edytuj] Przeczytałem wątek wspomniany powyżej na znormalizowanej
std::shared_ptr_access<>
propozycji. W odpowiedzi pojawiła się poprawkastd::allocate_shared<>
i przykład jej użycia. Dostosowałem go do poniższego szablonu fabryki i przetestowałem pod gcc C ++ 11/14/17. Działastd::enable_shared_from_this<>
również z, więc oczywiście byłoby lepiej niż moje oryginalne rozwiązanie w tej odpowiedzi. Oto jest ...[Orig] Znalazłem rozwiązanie za pomocą wspólnego konstruktora aliasingu wskaźnika. Pozwala to zarówno ctor, jak i dtor być prywatnymi, a także korzystać z końcowego specyfikatora.
Zauważ, że powyższe podejście nie działa dobrze,
std::enable_shared_from_this<>
ponieważ inicjałemstd::shared_ptr<>
jest opakowanie, a nie sam typ. Możemy rozwiązać ten problem za pomocą równoważnej klasy zgodnej z fabryką ...Wreszcie, ktoś powiedział, że clang narzekał, że Factory :: Type jest prywatny, gdy jest używany jako przyjaciel, więc po prostu upublicznij go, jeśli tak jest. Odsłonięcie go nie zaszkodzi.
źródło
Miałem ten sam problem, ale żadna z istniejących odpowiedzi nie była naprawdę zadowalająca, ponieważ muszę przekazać argumenty do chronionego konstruktora. Co więcej, muszę to zrobić dla kilku klas, z których każda przyjmuje inne argumenty.
W tym celu i bazując na kilku istniejących odpowiedziach, które wykorzystują podobne metody, przedstawiam ten mały samorodek:
źródło
Przyczyną problemu jest to, że jeśli funkcja lub klasa, którą zaprzyjaźniasz, wywołuje niższy poziom wywołań do twojego konstruktora, one również muszą być zaprzyjaźnione. std :: make_shared nie jest funkcją, która faktycznie wywołuje twojego konstruktora, więc zaprzyjaźnienie się z nim nie ma znaczenia.
std :: _ Ref_count_obj faktycznie wywołuje twojego konstruktora, więc musi być przyjacielem. Ponieważ jest to trochę niejasne, używam makra
W takim razie deklaracja klasowa wygląda dość prosto. Możesz utworzyć pojedyncze makro do deklarowania ptr i klasy, jeśli wolisz.
To właściwie ważna kwestia. Aby przenośny kod był łatwy w utrzymaniu, musisz ukryć jak najwięcej implementacji.
ukrywa, jak traktujesz trochę swój inteligentny wskaźnik, musisz koniecznie użyć swojego typedef. Ale jeśli zawsze musisz go utworzyć za pomocą make_shared, to pokonuje cel.
Powyższy przykład zmusza kod za pomocą twojej klasy do użycia twojego konstruktora inteligentnego wskaźnika, co oznacza, że jeśli przełączysz się na nowy smak inteligentnego wskaźnika, zmienisz deklarację klasy i masz przyzwoitą szansę na ukończenie. NIE zakładaj, że twój następny szef lub projekt użyje stl, ulepszenia itp. Planu jego zmiany w przyszłości.
Robiąc to przez prawie 30 lat, zapłaciłem wysoką cenę za czas, ból i skutki uboczne, aby naprawić to, gdy zrobiono to źle lata temu.
źródło
std::_Ref_count_obj
jest szczegółem implementacji. Oznacza to, że chociaż to rozwiązanie może teraz działać na Twoją platformę. Ale może nie działać dla innych i może przestać działać za każdym razem, gdy twój kompilator aktualizuje się, a może nawet po prostu zmieniając flagi kompilacji.Możesz użyć tego:
źródło
std::make_shared
.źródło