Yalnızca korumalı veya özel kurucuları olan bir sınıfta :: std :: make_shared'i nasıl arayabilirim?


187

Ben işe yaramazsa bu kodu var, ama niyet açık olduğunu düşünüyorum:

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();
}

Ama derlediğimde bu hatayı alıyorum:

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

Bu ileti temel olarak, şablon örnekleme yığındaki bazı rastgele yöntemlerin ::std::make_sharedkorunduğu için kurucuya erişemediğini söylüyor.

Ama gerçekten her ikisini de kullanmak ::std::make_sharedve kimsenin bu sınıfın a ::std::shared_ptr. Bunu başarmanın bir yolu var mı?


Yapıcıya ihtiyaç duyan işlevi derinlemesine arkadaş olarak işaretleyebilirsiniz, ancak bu taşınabilir olmayacaktır.
Dani

@Dani: Evet, taşınabilir bir çözüme sahip olmak güzel olurdu. Ama bu işe yarar.
şeye kadir

Yanıtlar:


109

Bu cevap muhtemelen daha iyi ve muhtemelen cevaplayacağım . Ama aynı zamanda daha çirkin bir yöntem buldum, ama yine de her şeyin hala satır içi olmasına izin verdim ve türetilmiş bir sınıf gerektirmiyor:

#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);
}

Edit 2017-01-06: Bu fikrin, bu fikrin tartışmacı yapıcılara açıkça ve basitçe genişletilebildiğini açıklığa kavuşturmak için değiştirdim, çünkü diğer insanlar bu satırlar boyunca cevaplar veriyordu ve bu konuda karışık görünüyordu.


14
Aslında, sadece anahtar olarak kullanılan anlamsız yapıların büyük bir hayranıyım . Bunu Luc'in çözümüne tercih ederim, ama bu benim mirasa karşı olan biailerim olabilir.
Matthieu M.

2
Kabul ettim, bunu daha çok seviyorum.
ildjarn

3
@Berkus: Öyleyse protectedyerine yap private. Ve "it" ile, this_is_privatebelki bu durumda yeniden adlandırılması gereken sınıfa atıfta bulunuyorum . Genellikle kodumda constructor_accessderim.
dalle

1
Ne yazık ki, kurucunuz gerçek parametreler alıyorsa bu çalışmaz; bu durumda {}, tür adına erişmeden (g ++ 4.9.0 ile test edilmiştir) özel etiketi iletebilirsiniz . Gerçek parametreler olmadan A, neden olduğu hakkında hiçbir fikrim olmamasına rağmen {} 'dan oluşturmaya çalışır ve başarısız olur. Ben this_is_private yapıcı özel yapma ve bir üye işlev imzası tür sızıntı sürece dışarıdan bu yönteme erişmek için yolu olmamalıdır, bunu oluşturmak için statik bir yöntem sağlamak düşünüyorum.
Stefan

3
Stefan, eğer this_is_privateözel bir ctor verirsen A sınıfını arkadaş edinebilirsin. Boşluğu kapatmak görünüyor.
Steven Kramer

78

std::make_shared20.7.2.2.6 paylaşılan_ptr oluşturma [util.smartptr.shared.create], paragraf 1'deki gereksinimlere bakarak :

Gerektirir: ifadesi ::new (pv) T(std::forward<Args>(args)...), pvtürü vardır void*ve tip bir nesne tutmak için uygun bir depolama noktaları T, iyi oluşturulacaktır. Abir paylaştırıcı olacaktır (17.6.3.5). Kopya kurucusu ve Aimhacısı istisnaları atmayacaktır.

İhtiyaç bu ifade açısından koşulsuz olarak belirtildiğinden ve kapsam gibi şeyler dikkate alınmadığından, arkadaşlık gibi hilelerin doğru olduğunu düşünüyorum.

Basit bir çözüm türetmektir A. Bu, Abir arayüz veya hatta polimorfik bir tip yapılmasını gerektirmez .

// 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
Oh, bu çok zekice bir cevap ve muhtemelen düşündüğümden daha iyi bir cevap.
şeye kadir

Yine de bir soru, shared_ptr bir concrete_A değil A'yı silmez ve bu da sorunlara neden olmaz mı?
şeye kadir

8
Ahh, bunun nedeni shared_ptr, örnekleme anında bir silmeyi depolamasıdır ve silmeyi kullanıyorsanız make_sharedkesinlikle doğru türü kullanmak zorundadır.
şeye kadir

1
@LucDanton Soru, başlığın da özel bir ctor istediğini belirttiği gibi arayüzlerle ilgili değil. Ayrıca, bu yüzden zaten bu soru üzerinde duruyorum. Özel bir ctor ve ham bir işaretçi döndüren bir oluşturma yöntemi olan machiavelli sınıfları olan bazı eski kod ve ben onları akıllı işaretçiler dönüştürmek çalışıyorum.
zahir

2
Bu yaklaşımı seviyorum (bunu kendim kullanıyorum) ama sanal bir yıkıcıya ihtiyacınız var. Argümanları olan yapıcılara iyi uzanır (sadece bir geçiş yapıcısı sağlayın). Eğer kullanarak Ve eğer korumalı yerine özel üstbilgi kullanıcılarına tamamen görünmez yapabilirsiniz.
Joe Steele

69

Muhtemelen en basit çözüm. Mohit Aron'un önceki cevabına dayanarak ve dlf'nin önerisini dahil etti.

#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
eğer Avarsayılan olmayan bir oluşturucusu yok bunları da açığa gerekir: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Bu, tüm özel kurucuları kurucu olarak Agörünür kılar make_shared_enabler. Yapıcılar miras özelliği ( using A::A;) kullanmak burada yardımcı olmuyor gibi görünüyor çünkü kurucular hala özel olacak.
anton_rh

2
@anton_rh: iç sınıflara şablon bağımsız değişkenleri ekleyemezsiniz. Buraya bakın .
bobbel

3
Hm ... Haklısın. Benim durumumda yapı yerel değildi, ama özel yapı oldu: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Buraya bakınız cpp.sh/65qbr .
16:31

Harika çalışıyor. Bunu kalıtsal bir özellik haline getirme şansı var mı, bu yüzden bu desenin birden çok kez tekrarlanması gerekmiyor mu? Özellikle varsayılan olmayan kurucuları ortaya çıkaran versiyon benim için çok ilginç olurdu. Varsayılan sürüm "yalnızca", A'yı sınıfı devralan sınıf ile her ne değiştirirse değiştiren bir sözdizimsel yapı gerektirir. Böyle bir şeyin farkında değilim, ama var olduğunu öğrenmek beni şaşırtmaz ...
Kjeld Schmidt

30

İşte bunun için temiz bir çözüm:

#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
Bunu severim. MakeSharedEnablerİçeride yerel olarak tanımlayarak biraz daha basit hale getirilebilir A::Create().
dlf

Harika fikir Mohit bana çok yardımcı oldu.
Jnana

12

Buna ne dersin?

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

13
Harika çalışıyor. Ancak ::std::make_shared, bir şeye paylaşılan_ptr yapmanın ötesinde işlevselliğe sahiptir. Nesne ile birlikte referans sayısını ayırır, böylece birbirlerine yakın yerleştirilirler. Gerçekten, gerçekten kullanmak istiyorum ::std::make_shared.
şeye kadir

Silinen atama ve kopya operatörleri bunu yasakladı
Dani

7
Sorunun tam olarak sorduğu soru olmasa da, bu gerçekten en basit yaklaşımdır. make_shared'in bazı güzel özellikleri var ve mümkün olan her yerde kullanmaya çalışıyorum, ancak bu durumda make_shared'in çalışma zamanı performans avantajlarının aslında kullanmak için gerekli olan ekstra kod karmaşıklığı ve töreninden ağır basmadığı görünüyor. Eğer gerçekten make_shared performansına ihtiyacınız varsa o zaman delirin, ama sadece shared_ptr yapıcısını kullanmanın basitliğini göz ardı etmeyin.
Kevin

Bellek sızıntıları konusunda dikkatli olun ... bu soruya bakın 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_;
};

Bu, yerel bir sınıfa dönüştürmek hoş bir dokunuş olsa da, Luc Danton'un cevabı ile büyük ölçüde aynı şey. Koda eşlik etmek için bazı açıklamalar bu çok daha iyi bir cevap olabilir.

Normalde, başlık dosyasına böyle küçük bir işlev yazmak istiyorum ama cc dosyasına yazmak istemiyorum. İkincisi, pratikte, #define SharedPtrCreate (T) şablonu <typename ... Arg> gibi görünen bir makro kullanıyorum
alfa

İyi cevap. Bunu IMPLEMENT_CREATE_SHARED (ClassName) gibi bir makroya bile koydum
ivan.ukr

8

Zaten verilen cevapları beğenmediğim için arama yapmaya karar verdim ve önceki cevaplar kadar genel olmayan bir çözüm buldum ama daha iyi (tm) seviyorum. Geçmişe bakıldığında, Omnifarius tarafından sağlanandan çok daha hoş değil ama bunu seven başka insanlar da olabilir :)

Bu benim tarafımdan icat edilmedi, ancak Jonathan Wakely'nin (GCC geliştiricisi) fikri.

Ne yazık ki tüm derleyiciler ile çalışmaz, çünkü std :: tahsis_shared uygulamasında küçük bir değişikliğe dayanır. Ancak bu değişiklik artık standart kütüphaneler için önerilen bir güncellemedir, bu nedenle gelecekte tüm derleyiciler tarafından desteklenebilir. GCC 4.7 üzerinde çalışır.

C ++ standart Kütüphane Çalışma Grubu değişiklik isteği burada: http://lwg.github.com/issues/lwg-active.html#2070

Örnek bir kullanıma sahip GCC yaması burada: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

Çözüm, özel kurucu ile sınıfa arkadaş olarak bildirilen özel bir ayırıcı ile std :: tahsis_shared (std :: make_shared yerine) kullanılması fikri üzerinde çalışır.

OP'den örnek şu şekilde görünecektir:

#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;
}

Üzerinde çalıştığım yardımcı programı temel alan daha karmaşık bir örnek. Bununla Luc'ın çözümünü kullanamadım. Ancak Omnifarius'un uyarlaması yapılabilir. Önceki örnekte herkesin buradaki MyAlloc kullanarak bir A nesnesi oluşturabildiği halde, create () yönteminin yanında A veya B oluşturmanın yolu yoktur.

#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

İdeal olarak, mükemmel çözüm C ++ standardına eklemeler gerektireceğini düşünüyorum. Andrew Schepler aşağıdakileri önermektedir:

( Tüm konu için buraya gidin )

Bir fikri boost :: iterator_core_access'ten ödünç alabiliriz. std::shared_ptr_accessHerkese açık veya korumalı üyeleri olmayan yeni bir sınıf öneririm ve std :: make_shared (args ...) ve std :: tahsis_shared (a, args ...) ifadelerini :: new (pv) T (forward (args) ...) ve ptr-> ~ T (), std :: shared_ptr_access bağlamında iyi biçimlendirilmiş olmalıdır.

Bir std :: shared_ptr_access uygulaması şöyle görünebilir:

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;
    };
}

kullanım

Yukarıdakiler standarda eklenirse / eklenirse, şunları yaparız:

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;
};

Bu da size standartta önemli bir katkı gibi geliyorsa, 2 sentinizi bağlantılı isocpp Google Grubu'na eklemekten çekinmeyin.


1
Bence bu standarda iyi bir ektir, ancak Google Grubuna katılmak ve yorum yapmak için zaman ayırıp benim o gruba ve yoruma dikkat etmem yeterli değil. :-)
şeye kadir

4

Bu iş parçacığının oldukça eski olduğunu fark ettim, ancak başka bir yerde göremediğim yapıcıya miras veya ekstra argümanlar gerektirmeyen bir cevap buldum. Ancak taşınabilir değil:

#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());
}

Windows ve Linux üzerinde test yaptım, farklı platformlar için ince ayar gerekebilir.


1
Ben taşınabilirlik eksikliği -1 için cazip. Diğer cevaplar (özellikle 'anahtar sınıf' cevaplar) oldukça zarif ve taşınabilir olmayan cevap çok çirkin. Taşınabilir olmayan cevabı kullanmanız için bir neden düşünemiyorum. Daha hızlı ya da bunun gibi bir şey değil.
şeye kadir

@Omnifarious Gerçekten taşınabilir değil ve ben tavsiye etmem, ama bunun aslında anlamsal olarak en doğru çözüm olduğuna inanıyorum. In my cevap , ben ekleyerek bir öneriye bağlamak std::shared_ptr_accessbir basit ve taşınabilir bir şekilde yukarıdaki yapmak için izin olarak görülebilir standardına.
Boris Dalstein

3

Birlikte çalışan sıkı sıkıya bağlı iki A ve B sınıfınız olduğunda ortaya çıkan daha kıllı ve ilginç bir sorun var.

A deyin "ana sınıf" ve B "köle" dir. B örneğini yalnızca A ile kısıtlamak istiyorsanız, B'nin yapıcısını özel ve B arkadaşını A ile böyle yaparsınız

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

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

Ne yazık ki std::make_shared<B>()bir yöntemden çağırma Aderleyicinin B::B()özel olmaktan şikayet edecek .

Buna benim çözümüm , özel kurucuya sahip olan ve içinde yapıcı ile arkadaş olan ve onunla herkese açık hale getiren ve bunun gibi argümanlarına ekleyen bir kamu Passkukla sınıfı (tıpkı gibi nullptr_t) yaratmaktır .BABPass

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

Ayrıca, argüman alan bir yapıcıyı etkinleştirmek istiyorsanız, bu biraz yardımcı olabilir.

#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

[Düzenle] Standart bir std::shared_ptr_access<>teklifte yukarıda belirtilen konu başlığını okudum . İçinde bir düzeltme std::allocate_shared<>ve kullanımının bir örneğini belirten bir yanıt vardı . Aşağıdaki fabrika şablonuna uyarladım ve gcc C ++ 11/14/17 altında test ettim. Aynı şekilde çalışır std::enable_shared_from_this<>, bu yüzden bu cevaptaki orijinal çözümüme tercih edilir. İşte burada...

#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] Paylaşılan işaretçi takma yapıcısını kullanarak bir çözüm buldum. Hem ctor hem de dtor'un özel olmasına ve son belirleyicinin kullanılmasına izin verir.

#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);
}

Yukarıdaki yaklaşımın iyi oynamadığına dikkat edin, std::enable_shared_from_this<>çünkü başlangıç std::shared_ptr<>, türün kendisi değil, sarıcıya aittir. Bunu fabrika ile uyumlu eşdeğer bir sınıfla ele alabiliriz ...

#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";
}

Son olarak, birisi clang'ın bir arkadaş olarak kullanıldığında Factory :: Type'ın özel olmaktan şikayet ettiğini söyledi, bu durumda sadece halka açık hale getirin. Teşhir etmek zarar vermez.


3

Aynı problemi yaşadım, ancak mevcut cevapların hiçbiri korumalı kurucuya argüman göndermem gerektiğinden gerçekten tatmin edici değildi. Dahası, bunu her biri farklı argümanlar alan birkaç sınıf için yapmam gerekiyor.

Bu amaçla ve hepsi benzer yöntemleri kullanan mevcut cevapların birkaçına dayanarak, bu küçük külçeyi sunuyorum:

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

Sorunun kökü, arkadaşınız işlev veya sınıf yapıcıya daha düşük düzeyde çağrı yaparsa, bunların da arkadaş olması gerektiğidir. std :: make_shared aslında kurucunuzu çağıran fonksiyon değildir, bu yüzden arkadaşlık etmek fark etmez.

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 aslında kurucunuzu çağırıyor, bu yüzden bir arkadaş olmalı. Bu biraz belirsiz olduğu için bir makro kullanıyorum

#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;

O zaman sınıf deklarasyonunuz oldukça basit görünüyor. İsterseniz ptr ve sınıfı bildirmek için tek bir makro yapabilirsiniz.

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

Bu aslında önemli bir konudur. Sürdürülebilir, taşınabilir kod oluşturmak için uygulamanın mümkün olduğunca çok bölümünü gizlemeniz gerekir.

typedef std::shared_ptr<A> APtr;

akıllı işaretçinizi nasıl ele aldığınızı gizler, typedef'inizi kullandığınızdan emin olmanız gerekir. Ancak make_shared kullanarak her zaman bir tane oluşturmanız gerekiyorsa, amacı yener.

Yukarıdaki örnek, akıllı işaretçi yapıcıyı kullanmak için sınıfınızı kullanarak kodu zorlar; bu, yeni bir akıllı işaretçi lezzetine geçerseniz, sınıf bildiriminizi değiştirdiğiniz ve bitirme şansınızın yüksek olduğu anlamına gelir. Bir sonraki patronunuzun veya projenizin bir gün değiştirmek için stl, boost vs. planını kullanacağını varsaymayın.

Bunu neredeyse 30 yıl boyunca yapıyorum, yıllar önce yanlış yapıldığında bunu onarmak için zaman, acı ve yan etkiler için büyük bir bedel ödedim.


2
std::_Ref_count_objbir uygulama detayıdır. Yani bu çözüm sizin için platformunuzda işinize yarayabilir. Ancak başkaları için çalışmayabilir ve derleyicinizin her güncellemesinde veya derleme bayraklarını değiştirseniz bile çalışmayı durdurabilir.
François Andrieux

-3

Bunu kullanabilirsiniz:

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
Kullanmaz 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;
}

Bu, sadece bu cevabın bir kopyasıdır: stackoverflow.com/a/27832765/167958
Herşey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.