Jak wywołać :: std :: make_shared na klasie z tylko chronionymi lub prywatnymi konstruktorami?


187

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_sharednie może uzyskać dostępu do konstruktora, ponieważ jest chroniona.

Ale naprawdę chcę używać obu tych elementów ::std::make_sharedi 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?


Możesz zaznaczyć funkcję, która wymaga konstruktora jako przyjaciela, ale nie będzie przenośna.
Dani

@Dani: Tak, byłoby miło mieć przenośne rozwiązanie. Ale to by działało.
Wszechobecny

Odpowiedzi:


109

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:

#include <memory>
#include <string>

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template <typename... T>
   static ::std::shared_ptr<A> create(T &&...args) {
      return ::std::make_shared<A>(this_is_private{0},
                                   ::std::forward<T>(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

::std::shared_ptr<A> bar()
{
   return A::create("George", 5);
}

::std::shared_ptr<A> errors()
{
   ::std::shared_ptr<A> retval;

   // Each of these assignments to retval properly generates errors.
   retval = A::create("George");
   retval = new A(A::this_is_private{0});
   return ::std::move(retval);
}

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.


14
Właściwie jestem wielkim fanem tych bezsensownych struktur używanych tylko jako klucze . Wolę to od rozwiązania Luca, ale może to być moje uprzedzenie do dziedziczenia.
Matthieu M.

2
Zgadzam się, ja też bardziej to lubię.
ildjarn

3
@Berkus: Więc zrób to protectedzamiast private. I przez „to” mam na myśli this_is_privateklasę, którą w takim przypadku należy zmienić. Zwykle nazywam to constructor_accessw moim kodzie.
dalle

1
Niestety nie działa to, jeśli twój konstruktor przyjmuje rzeczywiste parametry; w takim przypadku możesz po prostu przejść {}do prywatnego tagu bez dostępu do nazwy typu (testowane g ++ 4.9.0). Bez rzeczywistych parametrów próbuje skonstruować Az {}, 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.
Stefan

3
Stefan, jeśli dasz this_is_privateprywatnego doradcę, możesz uczynić z klasy A przyjaciela. Wygląda na to, żeby zamknąć lukę.
Steven Kramer

78

Patrząc na wymagania dotyczące std::make_shared20.7.2.2.6 tworzenia share_ptr [util.smartptr.shared.create], akapit 1:

Wymaga: Wyrażenie ::new (pv) T(std::forward<Args>(args)...), w którym pvma typ void*i wskazuje miejsce do przechowywania odpowiednie do przechowywania obiektu typu T, musi być dobrze uformowane. Apowinien być alokatorem (17.6.3.5). Konstruktor kopii i destruktor Anie będą zgłaszać wyjątków.

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ć tworzenia Ainterfejsu ani nawet typu polimorficznego.

// interface in header
std::shared_ptr<A> make_a();

// implementation in source
namespace {

struct concrete_A: public A {};

} // namespace

std::shared_ptr<A>
make_a()
{
    return std::make_shared<concrete_A>();
}

1
Och, to bardzo sprytna odpowiedź i być może lepsza niż inna, o której myślałem.
Wszechobecny

Jedno pytanie, czy shared_ptr nie usunie A, a nie konkretnego_A, i czy nie może to powodować problemów?
Wszechobecny

8
Achh, to dlatego, że shared_ptrprzechowuje urządzenie do usuwania w momencie tworzenia wystąpienia, a jeśli używasz urządzenia make_shareddo usuwania, absolutnie musisz używać odpowiedniego typu.
Wszechobecny

1
@LucDanton Pytanie nie dotyczy interfejsów, ponieważ tytuł sugeruje, że prosi również o prywatnego ctora. Poza tym dlatego i tak mam pytanie. Stary kod z klasami machiavelli, który ma prywatny ctor i metodę tworzenia zwracającą surowy wskaźnik, a ja próbuję przekonwertować je na inteligentne wskaźniki.
zahir

2
Lubię to podejście (korzystam z niego osobiście), ale potrzebujesz wirtualnego destruktora. Rozciąga się również na konstruktory z argumentami (wystarczy podać konstruktor tranzytowy). A jeśli używasz chronionego, a nie prywatnego , możesz uczynić go całkowicie niewidocznym dla użytkowników nagłówka.
Joe Steele,

69

Prawdopodobnie najprostsze rozwiązanie. Na podstawie poprzedniej odpowiedzi Mohita Arona i uwzględnienia sugestii dlf.

#include <memory>

class A
{
public:
    static std::shared_ptr<A> create()
    {
        struct make_shared_enabler : public A {};

        return std::make_shared<make_shared_enabler>();
    }

private:
    A() {}  
};

5
jeśli Ama 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ą Awidoczne jako make_shared_enablerkonstruktory. using A::A;Wydaje się, że użycie funkcji dziedziczenia konstruktorów ( ) nie pomaga tutaj, ponieważ konstruktory będą nadal prywatne.
anton_rh

2
@anton_rh: nie można dodawać argumentów szablonów do klas wewnętrznych. Zobacz tutaj .
bobbel

3
Hm ... Wygląda na to, że masz rację. W moim przypadku nie było lokalne struct, ale był to prywatny struct: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Zobacz tutaj cpp.sh/65qbr .
anton_rh

To działa świetnie. Czy jest jakaś szansa, aby uczynić tę dziedziczną właściwość, więc ten wzór nie musi być powtarzany wiele razy? Szczególnie interesująca byłaby wersja, która odsłania konstruktory inne niż domyślne. Wersja domyślna wymagałaby „jedynie” konstrukcji syntaktycznej, która zastępuje A dowolną klasą, która dziedziczy klasę. Nie jestem świadomy czegoś takiego, ale nie zdziwiłbym się, gdy dowiedział się, że istnieje ...
Kjeld Schmidt,

30

Oto fajne rozwiązanie tego:

#include <memory>

class A {
   public:
     static shared_ptr<A> Create();

   private:
     A() {}

     struct MakeSharedEnabler;   
 };

struct A::MakeSharedEnabler : public A {
    MakeSharedEnabler() : A() {
    }
};

shared_ptr<A> A::Create() {
    return make_shared<MakeSharedEnabler>();
}

3
Lubię to. Można to nieco uprościć, definiując MakeSharedEnablerlokalnie wewnątrz A::Create().
dlf

Niesamowity pomysł, Mohit, bardzo mi pomógł.
Jnana

12

Co powiesz na to?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}

13
To działa świetnie. Ale ::std::make_sharedfunkcjonalność 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.
Wszechobecny

Usunięci operatorzy przypisania i kopiowania zabraniają tego
Dani,

7
To jest naprawdę najprostsze podejście, nawet jeśli tak naprawdę nie jest to pytanie. make_shared ma kilka fajnych cech i staram się go używać wszędzie tam, gdzie to możliwe, ale w tej sytuacji wydaje się całkiem prawdopodobne, że zalety make_shared w wydajności w czasie wykonywania nie przeważają nad dodatkową złożonością kodu i ceremonią faktycznie wymaganą do jego użycia. Jeśli naprawdę potrzebujesz wydajności make_shared, zwariuj, ale nie zapomnij o prostocie używania konstruktora shared_ptr.
Kevin

Uważaj jednak na wycieki pamięci ... zobacz to pytanie stackoverflow.com/a/14837300/2149539
dgmz

12
struct A {
public:
  template<typename ...Arg> std::shared_ptr<A> static create(Arg&&...arg) {
    struct EnableMakeShared : public A {
      EnableMakeShared(Arg&&...arg) :A(std::forward<Arg>(arg)...) {}
    };
    return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
  }
  void dump() const {
    std::cout << a_ << std::endl;
  }
private:
  A(int a) : a_(a) {}
  A(int i, int j) : a_(i + j) {}
  A(std::string const& a) : a_(a.size()) {}
  int a_;
};

Jest to w dużej mierze to samo, co odpowiedź Luca Dantona, chociaż przekształcenie go w klasę lokalną jest miłym akcentem. Niektóre wyjaśnienia towarzyszące kodowi mogą sprawić, że będzie to znacznie lepsza odpowiedź.

Zwykle chcę zapisać tak małą funkcję w pliku nagłówkowym, ale nie w pliku cc. Po drugie, w praktyce używam makra, które wygląda jak #define SharedPtrCreate (T) szablon <typename ... Arg> .....
alpha

Dobra odpowiedź. Umieściłbym to nawet w makrze o nazwie IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

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:

#include <memory>

template<typename Private>
struct MyAlloc : std::allocator<Private>
{
    void construct(void* p) { ::new(p) Private(); }
};

class A {
    public:
        static ::std::shared_ptr<A> create() {
            return ::std::allocate_shared<A>(MyAlloc<A>());
        }

    protected:
        A() {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;

        friend struct MyAlloc<A>;
};

int main() {
    auto p = A::create();
    return 0;
}

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 ().

#include <memory>

template<typename T>
class safe_enable_shared_from_this : public std::enable_shared_from_this<T>
{
    public:
    template<typename... _Args>
        static ::std::shared_ptr<T> create(_Args&&... p_args) {
            return ::std::allocate_shared<T>(Alloc(), std::forward<_Args>(p_args)...);
        }

    protected:
    struct Alloc : std::allocator<T>
    {  
        template<typename _Up, typename... _Args>
        void construct(_Up* __p, _Args&&... __args)
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    };
    safe_enable_shared_from_this(const safe_enable_shared_from_this&) = delete;
    safe_enable_shared_from_this& operator=(const safe_enable_shared_from_this&) = delete;
};

class A : public safe_enable_shared_from_this<A> {
    private:
        A() {}
        friend struct safe_enable_shared_from_this<A>::Alloc;
};

class B : public safe_enable_shared_from_this<B> {
    private:
        B(int v) {}
        friend struct safe_enable_shared_from_this<B>::Alloc;
};

int main() {
    auto a = A::create();
    auto b = B::create(5);
    return 0;
}

6

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)

możemy pożyczyć pomysł z boost :: iterator_core_access. Proponuję nową klasę std::shared_ptr_accessbez publicznych lub chronionych członków, i sprecyzuję, że dla std :: make_shared (args ...) i std :: przyznaj_shared (a, args ...), wyrażenia :: new (pv) T (forward (args) ...) i ptr-> ~ T () muszą być poprawnie sformułowane w kontekście std :: shared_ptr_access.

Implementacja std :: shared_ptr_access może wyglądać następująco:

namespace std {
    class shared_ptr_access
    {
        template <typename _T, typename ... _Args>
        static _T* __construct(void* __pv, _Args&& ... __args)
        { return ::new(__pv) _T(forward<_Args>(__args)...); }

        template <typename _T>
        static void __destroy(_T* __ptr) { __ptr->~_T(); }

        template <typename _T, typename _A>
        friend class __shared_ptr_storage;
    };
}

Stosowanie

Jeśli / kiedy powyższe zostanie dodane do standardu, po prostu zrobilibyśmy:

class A {
public:
   static std::shared_ptr<A> create() {
      return std::make_shared<A>();
   }

 protected:
   friend class std::shared_ptr_access;
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

Jeśli wydaje ci się to również ważnym dodatkiem do standardu, dodaj 2 centy do połączonej grupy dyskusyjnej isocpp Google.


1
Myślę, że to dobry dodatek do standardu, ale nie jest wystarczająco ważny, aby poświęcić czas na dołączenie do grupy Google i komentowanie, a następnie zwrócenie uwagi na tę grupę i komentarz. :-)
Wszechobecny

4

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:

#include <memory>

#if defined(__cplusplus) && __cplusplus >= 201103L
#define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator<test>::construct<test>(test*);
#elif defined(_WIN32) || defined(WIN32)
#if defined(_MSC_VER) && _MSC_VER >= 1800
#define ALLOW_MAKE_SHARED(x) friend class std::_Ref_count_obj;
#else
#error msc version does not suport c++11
#endif
#else
#error implement for platform
#endif

class test {
    test() {}
    ALLOW_MAKE_SHARED(test);
public:
    static std::shared_ptr<test> create() { return std::make_shared<test>(); }

};
int main() {
    std::shared_ptr<test> t(test::create());
}

Testowałem na Windowsie i Linuksie, może to wymagać dostosowania na różnych platformach.


1
Kusi mnie do -1 z powodu braku przenośności. Inne odpowiedzi (szczególnie odpowiedzi „klasy kluczowej”) są dość eleganckie, a odpowiedź nieprzenośna bardzo brzydka. Nie mogę wymyślić powodu, dla którego użyłabyś nieprzenośnej odpowiedzi. To nie jest szybsze ani nic takiego.
Wszechobecny

@Omnifarious To jest rzeczywiście nieprzenośne i nie poleciłbym, ale wierzę, że jest to w rzeczywistości najbardziej poprawne semantycznie rozwiązanie. W mojej odpowiedzi odsyłam do propozycji dodania std::shared_ptr_accessdo standardu, co można uznać za pozwalające na wykonanie powyższego w prosty i przenośny sposób.
Boris Dalstein,

3

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

class B
{
public:
    // B your methods...

private:
    B();
    friend class A;
};

Niestety wywołanie std::make_shared<B>()z metody Aspowoduje, że kompilator narzeka na B::B()prywatność.

Moim rozwiązaniem jest utworzenie publicznej Passklasy manekina (podobnie jak nullptr_t) wewnątrz, Bktóra ma prywatnego konstruktora i jest przyjazna, Ai czyni Bkonstruktorem publicznym i dodaje Passdo swoich argumentów, takich jak ten.

class B
{
public:
  class Pass
  {
    Pass() {}
    friend class A;
  };

  B(Pass, int someArgument)
  {
  }
};

class A
{
public:
  A()
  {
    // This is valid
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

class C
{
public:
  C()
  {
    // This is not
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

3

Jeśli chcesz również włączyć konstruktora, który pobiera argumenty, może to trochę pomóc.

#include <memory>
#include <utility>

template<typename S>
struct enable_make : public S
{
    template<typename... T>
    enable_make(T&&... t)
        : S(std::forward<T>(t)...)
    {
    }
};

class foo
{
public:
    static std::unique_ptr<foo> create(std::unique_ptr<int> u, char const* s)
    {
        return std::make_unique<enable_make<foo>>(std::move(u), s);
    }
protected:
    foo(std::unique_ptr<int> u, char const* s)
    {
    }
};

void test()
{
    auto fp = foo::create(std::make_unique<int>(3), "asdf");
}

3

[Edytuj] Przeczytałem wątek wspomniany powyżej na znormalizowanej std::shared_ptr_access<>propozycji. W odpowiedzi pojawiła się poprawka std::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ła std::enable_shared_from_this<>również z, więc oczywiście byłoby lepiej niż moje oryginalne rozwiązanie w tej odpowiedzi. Oto jest ...

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        return std::allocate_shared<T>(Alloc<T>(), std::forward<A>(args)...);
    }
private:
    template<typename T>
    struct Alloc : std::allocator<T> {
        template<typename U, typename... A>
        void construct(U* ptr, A&&... args) {
            new(ptr) U(std::forward<A>(args)...);
        }
        template<typename U>
        void destroy(U* ptr) {
            ptr->~U();
        }
    };  
};

class X final : public std::enable_shared_from_this<X> {
    friend class Factory;
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(int) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto p1 = Factory::make_shared<X>(42);
    auto p2 = p1->shared_from_this();
    std::cout << "p1=" << p1 << "\n"
              << "p2=" << p2 << "\n"
              << "count=" << p1.use_count() << "\n";
}

[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.

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        return std::shared_ptr<T>(ptr, &ptr->type);
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
};

class X final {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = Factory::make_shared<X>(42);
}

Zauważ, że powyższe podejście nie działa dobrze, std::enable_shared_from_this<>ponieważ inicjałem std::shared_ptr<>jest opakowanie, a nie sam typ. Możemy rozwiązać ten problem za pomocą równoważnej klasy zgodnej z fabryką ...

#include <iostream>
#include <memory>

template<typename T>
class EnableShared {
    friend class Factory;  // factory access
public:
    std::shared_ptr<T> shared_from_this() { return weak.lock(); }
protected:
    EnableShared() = default;
    virtual ~EnableShared() = default;
    EnableShared<T>& operator=(const EnableShared<T>&) { return *this; }  // no slicing
private:
    std::weak_ptr<T> weak;
};

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        auto alt = std::shared_ptr<T>(ptr, &ptr->type);
        assign(std::is_base_of<EnableShared<T>, T>(), alt);
        return alt;
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
    template<typename T>
    static void assign(std::true_type, const std::shared_ptr<T>& ptr) {
        ptr->weak = ptr;
    }
    template<typename T>
    static void assign(std::false_type, const std::shared_ptr<T>&) {}
};

class X final : public EnableShared<X> {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = ptr1->shared_from_this();
    std::cout << "ptr1=" << ptr1.get() << "\nptr2=" << ptr2.get() << "\n";
}

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.


3

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:

template < typename Object, typename... Args >
inline std::shared_ptr< Object >
protected_make_shared( Args&&... args )
{
  struct helper : public Object
  {
    helper( Args&&... args )
      : Object{ std::forward< Args >( args )... }
    {}
  };

  return std::make_shared< helper >( std::forward< Args >( args )... );
}

1

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.

class A;
typedef std::shared_ptr<A> APtr;
class A
{
    template<class T>
    friend class std::_Ref_count_obj;
public:
    APtr create()
    {
        return std::make_shared<A>();
    }
private:
    A()
    {}
};

std :: _ Ref_count_obj faktycznie wywołuje twojego konstruktora, więc musi być przyjacielem. Ponieważ jest to trochę niejasne, używam makra

#define SHARED_PTR_DECL(T) \
class T; \
typedef std::shared_ptr<T> ##T##Ptr;

#define FRIEND_STD_MAKE_SHARED \
template<class T> \
friend class std::_Ref_count_obj;

W takim razie deklaracja klasowa wygląda dość prosto. Możesz utworzyć pojedyncze makro do deklarowania ptr i klasy, jeśli wolisz.

SHARED_PTR_DECL(B);
class B
{
    FRIEND_STD_MAKE_SHARED
public:
    BPtr create()
    {
        return std::make_shared<B>();
    }
private:
    B()
    {}
};

To właściwie ważna kwestia. Aby przenośny kod był łatwy w utrzymaniu, musisz ukryć jak najwięcej implementacji.

typedef std::shared_ptr<A> APtr;

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.


2
std::_Ref_count_objjest 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.
François Andrieux

-3

Możesz użyć tego:

class CVal
{
    friend std::shared_ptr<CVal>;
    friend std::_Ref_count<CVal>;
public:
    static shared_ptr<CVal> create()
    {
        shared_ptr<CVal> ret_sCVal(new CVal());
        return ret_sCVal;
    }

protected:
    CVal() {};
    ~CVal() {};
};

1
Nie korzysta std::make_shared.
Brian

-3
#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
private:
    A(){}
    explicit A(int a):m_a(a){}
public:
    template <typename... Args>
    static std::shared_ptr<A> create(Args &&... args)
    {
        class make_shared_enabler : public A
        {
        public:
            make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...){}
        };
        return std::make_shared<make_shared_enabler>(std::forward<Args>(args)...);
    }

    int val() const
    {
        return m_a;
    }
private:
    int m_a=0;
};

int main(int, char **)
{
    std::shared_ptr<A> a0=A::create();
    std::shared_ptr<A> a1=A::create(10);
    std::cout << a0->val() << " " << a1->val() << std::endl;
    return 0;
}

To tylko
kopia
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.