Bagaimana saya memanggil :: std :: make_share di kelas dengan hanya konstruktor yang dilindungi atau pribadi?


187

Saya memiliki kode ini yang tidak berfungsi, tetapi saya pikir maksudnya jelas:

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

Tapi saya mendapatkan kesalahan ini ketika saya kompilasi:

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

Pesan ini pada dasarnya mengatakan bahwa beberapa metode acak turun di tumpukan contoh template dari ::std::make_sharedtidak dapat mengakses konstruktor karena itu dilindungi.

Tapi saya benar-benar ingin menggunakan keduanya ::std::make_shareddan mencegah siapa pun membuat objek kelas ini yang tidak diarahkan oleh a ::std::shared_ptr. Apakah ada cara untuk mencapai ini?


Anda dapat menandai fungsi jauh di bawah yang membutuhkan konstruktor sebagai teman, tetapi itu tidak akan portabel.
Dani

@Dani: Ya, alangkah baiknya memiliki solusi portabel. Tapi itu akan berhasil.
Mahakuasa

Jawaban:


109

Jawaban ini mungkin lebih baik, dan yang kemungkinan besar akan saya terima. Tapi saya juga datang dengan metode yang lebih buruk, tetapi masih membiarkan semuanya masih sejajar dan tidak memerlukan kelas turunan:

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

Sunting 2017-01-06: Saya mengubah ini untuk memperjelas bahwa ide ini jelas dan sederhana untuk konstruktor yang mengambil argumen karena orang lain memberikan jawaban di sepanjang garis itu dan tampak bingung tentang hal ini.


14
Sebenarnya, saya penggemar berat dari struktur tak berarti yang hanya digunakan sebagai kunci . Saya lebih suka ini daripada solusi Luc, tetapi itu mungkin merupakan biografi saya terhadap warisan.
Matthieu M.

2
Setuju, aku juga suka ini.
ildjarn

3
@Berkus: Lalu membuatnya protectedbukan private. Dan dengan "itu", saya merujuk ke this_is_privatekelas, yang mungkin harus diganti namanya dalam kasus seperti itu. Saya biasanya menyebutnya constructor_accessdalam kode saya.
dalle

1
Sayangnya ini tidak berfungsi jika konstruktor Anda mengambil parameter nyata; dalam hal ini Anda dapat dengan mudah memberikan {}tag pribadi tanpa memiliki akses ke nama jenis (diuji dengan g ++ 4.9.0). Tanpa parameter nyata, ia mencoba membangun Adari {}, meskipun saya tidak tahu mengapa, dan gagal. Saya pikir menjadikan konstruktor this_is_private ini pribadi dan menyediakan metode statis untuk membuatnya memperbaikinya, karena seharusnya tidak ada cara untuk mengakses metode ini dari luar kecuali jika Anda membocorkan jenis tanda tangan fungsi anggota.
Stefan

3
Stefan, jika kamu memberikan this_is_privatekartu pribadi kamu bisa membuat teman kelas A. Tampaknya menutup celah itu.
Steven Kramer

78

Melihat persyaratan untuk std::make_shareddalam penciptaan 20.7.2.2.6 shared_ptr [util.smartptr.shared.create], paragraf 1:

Membutuhkan: Ekspresi ::new (pv) T(std::forward<Args>(args)...), di mana pvmemiliki tipe void*dan titik penyimpanan yang sesuai untuk menampung objek tipe T, harus terbentuk dengan baik. Aakan menjadi pengalokasi (17.6.3.5). Copy constructor dan destructor Atidak akan membuang pengecualian.

Karena persyaratannya ditentukan tanpa syarat dalam hal ekspresi itu dan hal-hal seperti ruang lingkup tidak diperhitungkan, saya pikir trik seperti persahabatan benar-benar keluar.

Solusi sederhana untuk diturunkan A. Ini tidak perlu mengharuskan membuat Aantarmuka atau bahkan tipe polimorfik.

// 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, itu jawaban yang sangat cerdas, dan mungkin lebih baik daripada yang lain yang saya pikirkan.
Mahakuasa

Namun satu pertanyaan, bukankah shared_ptr akan menghapus A dan bukan concrete_A, dan tidak bisakah ini menyebabkan masalah?
Mahakuasa

8
Ahh, itu karena shared_ptrmenyimpan deleter pada saat instantiation, dan jika Anda menggunakan make_shareddeleter harus menggunakan tipe yang tepat.
Mahakuasa

1
@LucDanton Pertanyaan bukan tentang antarmuka, seperti judulnya dia juga meminta aktor pribadi. Lagipula, karena itu aku tetap pada pertanyaan ini. Beberapa kode lama memiliki kelas machiavelli, yang memiliki ctor pribadi dan metode buat yang mengembalikan pointer mentah, dan saya mencoba untuk mengubahnya menjadi pointer pintar.
zahir

2
Saya suka pendekatan ini (menggunakannya sendiri) tetapi Anda memang membutuhkan destruktor virtual. Itu meluas baik untuk konstruktor dengan argumen (hanya menyediakan konstruktor passthrough). Dan jika Anda menggunakan dilindungi daripada pribadi Anda dapat membuatnya benar-benar tidak terlihat oleh pengguna header.
Joe Steele

69

Mungkin solusi paling sederhana. Berdasarkan jawaban sebelumnya oleh Mohit Aron dan memasukkan saran 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
jika Amemiliki konstruktor non-default Anda juga akan perlu untuk mengekspos mereka: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Ini membuat semua konstruktor pribadi Aterlihat sebagai make_shared_enablerkonstruktor. Menggunakan fitur pewarisan konstruktor ( using A::A;) tampaknya tidak membantu di sini karena konstruktor akan tetap pribadi.
anton_rh

2
@anton_rh: Anda tidak bisa menambahkan argumen templat ke kelas dalam. Lihat di sini .
bobbel

3
Hm ... Sepertinya Anda benar. Dalam kasus saya struct tidak lokal, tapi struct pribadi: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Lihat di sini cpp.sh/65qbr .
anton_rh

Ini sangat bagus. Apakah ada kemungkinan menjadikan ini properti yang dapat diwarisi, sehingga pola ini tidak harus diulang beberapa kali? Khususnya versi yang memperlihatkan konstruktor non-standar akan sangat menarik bagi saya. Versi default akan "hanya" memerlukan beberapa konstruksi sintaksis yang menggantikan A dengan apa pun kelas yang mewarisi kelas. Saya tidak mengetahui hal seperti itu, tetapi saya tidak akan terkejut mengetahui bahwa itu ada ...
Kjeld Schmidt

30

Inilah solusi yang rapi untuk ini:

#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
Saya suka ini. Itu bisa dibuat sedikit lebih sederhana dengan mendefinisikan MakeSharedEnablersecara lokal di dalamnya A::Create().
dlf

Ide yang luar biasa Mohit sangat membantu saya.
Jnana

12

Bagaimana dengan ini?

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

13
Itu bagus sekali. Tetapi ::std::make_sharedmemiliki fungsionalitas di atas dan di luar hanya membuat shared_ptr untuk sesuatu. Ini mengalokasikan jumlah referensi bersama dengan objek sehingga mereka berada dekat satu sama lain. Saya benar-benar ingin menggunakannya ::std::make_shared.
Mahakuasa

Operator assigment dan copy yang dihapus melarang ini
Dani

7
Ini benar-benar pendekatan yang paling mudah, meskipun sebenarnya bukan apa yang ditanyakan. make_share memang memiliki beberapa karakteristik yang bagus dan saya mencoba menggunakannya sedapat mungkin, tetapi dalam situasi ini tampaknya sangat mungkin bahwa keuntungan kinerja run-time dari make_share tidak melebihi kompleksitas kode tambahan dan upacara sebenarnya diperlukan untuk menggunakannya. Jika Anda benar-benar membutuhkan kinerja make_ shared maka gila, tetapi jangan mengabaikan kesederhanaan hanya menggunakan konstruktor shared_ptr.
Kevin

Hati-hati dengan kebocoran memori ... lihat pertanyaan ini 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_;
};

Ini sebagian besar sama dengan jawaban Luc Danton, meskipun mengubahnya menjadi kelas lokal adalah sentuhan yang bagus. Beberapa penjelasan untuk menyertai kode bisa membuat ini menjadi jawaban yang jauh lebih baik.

Biasanya, saya ingin menulis fungsi kecil dalam file header tetapi bukan file cc. Kedua, dalam prakteknya, saya menggunakan makro yang terlihat seperti #define SharedPtrCreate (T) template <typename ... Arg> .....
alpha

Jawaban yang bagus. Saya bahkan akan memasukkannya ke dalam makro yang disebut seperti IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

Karena saya tidak suka jawaban yang sudah disediakan, saya memutuskan untuk mencari dan menemukan solusi yang tidak generik seperti jawaban sebelumnya tetapi saya lebih menyukainya (tm). Kalau dipikir-pikir itu tidak jauh lebih bagus daripada yang disediakan oleh Omnifarius tetapi mungkin ada orang lain yang menyukainya :)

Ini tidak ditemukan oleh saya, tetapi itu adalah ide Jonathan Wakely (pengembang GCC).

Sayangnya itu tidak bekerja dengan semua kompiler karena bergantung pada perubahan kecil dalam implementasi std :: alokasiate_share. Tetapi perubahan ini sekarang merupakan pembaruan yang diusulkan untuk pustaka standar, sehingga mungkin didukung oleh semua kompiler di masa depan. Ini bekerja pada GCC 4.7.

Permintaan perubahan Kelompok Kerja Perpustakaan C ++ standar ada di sini: http://lwg.github.com/issues/lwg-active.html#2070

Patch GCC dengan contoh penggunaan ada di sini: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

Solusinya bekerja pada ide untuk menggunakan std :: dialokasikanate_share (bukan std :: make_share) dengan pengalokasi kustom yang dideklarasikan teman ke kelas dengan konstruktor pribadi.

Contoh dari OP akan terlihat seperti ini:

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

Contoh yang lebih kompleks yang didasarkan pada utilitas yang sedang saya kerjakan. Dengan ini saya tidak bisa menggunakan solusi Luc. Tapi yang oleh Omnifarius bisa diadaptasi. Bukan berarti sementara dalam contoh sebelumnya semua orang dapat membuat objek A menggunakan MyAlloc di sini tidak ada cara untuk membuat A atau B selain metode 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

Idealnya, saya pikir solusi sempurna akan membutuhkan penambahan standar C ++. Andrew Schepler mengusulkan yang berikut:

(Buka di sini untuk seluruh utas)

kita dapat meminjam ide dari boost :: iterator_core_access. Saya mengusulkan kelas barustd::shared_ptr_access tanpa anggota publik atau yang dilindungi, dan untuk menentukan bahwa untuk std :: make_share (args ...) dan std :: alokasi_share (a, args ...), ekspresi :: baru (pv) T (forward (args) ...) dan ptr-> ~ T () harus dibentuk dengan baik dalam konteks std :: shared_ptr_access.

Implementasi std :: shared_ptr_access mungkin terlihat seperti:

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

Pemakaian

Jika / ketika hal di atas ditambahkan ke standar, kita cukup melakukan:

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

Jika ini juga kedengarannya merupakan tambahan penting untuk standar Anda, jangan ragu untuk menambahkan 2 sen Anda ke Grup Google yang terhubung.


1
Saya pikir ini merupakan tambahan yang baik untuk standar, tetapi itu tidak cukup penting bagi saya untuk meluangkan waktu untuk bergabung dengan Google Group dan berkomentar dan kemudian memperhatikan grup itu dan komentarnya. :-)
Omnifarious

4

Saya menyadari bahwa utas ini agak lama, tetapi saya menemukan jawaban yang tidak memerlukan warisan atau argumen tambahan kepada konstruktor yang tidak dapat saya lihat di tempat lain. Itu tidak portabel:

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

Saya telah menguji di windows dan linux, mungkin perlu mengubah untuk platform yang berbeda.


1
Saya tergoda untuk -1 karena kurangnya portabilitas. Jawaban lain (terutama jawaban 'kelas kunci') cukup elegan dan jawaban non-portabel sangat jelek. Saya tidak bisa memikirkan alasan mengapa Anda menggunakan jawaban non-portabel. Ini tidak lebih cepat atau semacamnya.
Mahakuasa

@Omnifarious Ini memang non-portabel dan saya tidak akan merekomendasikan, tapi saya percaya ini sebenarnya solusi semantik yang paling benar. Dalam jawaban saya , saya menautkan ke proposal penambahan std::shared_ptr_accesske standar, yang dapat dilihat memungkinkan untuk melakukan hal di atas dengan cara yang sederhana dan portabel.
Boris Dalstein

3

Ada masalah yang lebih berbulu dan menarik yang terjadi ketika Anda memiliki dua kelas A dan B yang saling terkait yang bekerja sama.

Say A adalah "kelas master" dan B "budaknya". Jika Anda ingin membatasi instantiasi B hanya menjadi A, Anda akan membuat konstruktor B menjadi pribadi, dan teman B ke A menyukai ini

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

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

Sayangnya memanggil std::make_shared<B>()dari metode Aakan membuat kompiler mengeluh tentang B::B()menjadi pribadi.

Solusi saya untuk ini adalah membuat Passkelas boneka publik (seperti nullptr_t) di dalam Byang memiliki konstruktor pribadi dan berteman dengan Adan membuat Bkonstruktor publik dan menambah Passargumennya, seperti ini.

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

Jika Anda juga ingin mengaktifkan konstuktor yang mengambil argumen, ini mungkin sedikit membantu.

#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

[Sunting] Saya membaca utas yang disebutkan di atas pada std::shared_ptr_access<>proposal standar . Di dalamnya ada tanggapan mencatat perbaikan std::allocate_shared<>dan contoh penggunaannya. Saya telah mengadaptasinya ke templat pabrik di bawah ini, dan mengujinya di bawah gcc C ++ 11/14/17. Ini bekerja dengan std::enable_shared_from_this<>baik, jadi jelas akan lebih baik daripada solusi asli saya dalam jawaban ini. Ini dia...

#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] Saya menemukan solusi menggunakan konstruktor aliasing pointer bersama. Hal ini memungkinkan ctor dan dtor untuk menjadi pribadi, serta penggunaan specifier akhir.

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

Perhatikan bahwa pendekatan di atas tidak cocok std::enable_shared_from_this<>karena inisial std::shared_ptr<>adalah untuk pembungkus dan bukan tipe itu sendiri. Kami dapat mengatasi ini dengan kelas setara yang kompatibel dengan pabrik ...

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

Terakhir, seseorang berkata dentang mengeluh tentang Factory :: Type menjadi pribadi ketika digunakan sebagai teman, jadi buat publik jika itu yang terjadi. Mengeksposnya tidak ada salahnya.


3

Saya memiliki masalah yang sama, tetapi tidak ada jawaban yang ada yang benar-benar memuaskan karena saya perlu menyampaikan argumen kepada konstruktor yang dilindungi. Selain itu, saya perlu melakukan ini untuk beberapa kelas, masing-masing mengambil argumen yang berbeda.

Untuk itu, dan membangun beberapa jawaban yang ada yang semuanya menggunakan metode serupa, saya sajikan sedikit nugget ini:

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

Akar masalahnya adalah bahwa jika fungsi atau kelas teman Anda membuat panggilan tingkat rendah ke konstruktor Anda, mereka harus berteman juga. std :: make_share bukan fungsi yang memanggil konstruktor Anda, jadi berteman itu tidak ada bedanya.

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 sebenarnya memanggil konstruktor Anda, jadi ia harus menjadi teman. Karena itu agak tidak jelas, saya menggunakan makro

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

Kemudian deklarasi kelas Anda terlihat cukup sederhana. Anda bisa membuat makro tunggal untuk mendeklarasikan ptr dan kelas jika Anda mau.

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

Ini sebenarnya masalah penting. Untuk membuat kode portabel yang dapat dipelihara, Anda perlu menyembunyikan sebanyak mungkin implementasi.

typedef std::shared_ptr<A> APtr;

menyembunyikan bagaimana Anda menangani pointer cerdas Anda sedikit, Anda harus yakin untuk menggunakan typedef Anda. Tetapi jika Anda selalu harus membuatnya menggunakan make_share, itu mengalahkan tujuannya.

Contoh di atas memaksa kode menggunakan kelas Anda untuk menggunakan konstruktor penunjuk pintar Anda, yang berarti bahwa jika Anda beralih ke penunjuk cerdas yang baru, Anda mengubah deklarasi kelas Anda dan Anda memiliki peluang yang layak untuk diselesaikan. JANGAN berasumsi bahwa bos atau proyek Anda berikutnya akan menggunakan stl, boost, dll. Rencanakan untuk mengubahnya suatu hari nanti.

Melakukan ini selama hampir 30 tahun, saya telah membayar harga yang besar dalam waktu, rasa sakit dan efek samping untuk memperbaiki ini ketika itu dilakukan salah tahun yang lalu.


2
std::_Ref_count_objadalah detail implementasi. Itu berarti sementara solusi ini mungkin bekerja untuk Anda, untuk saat ini, di platform Anda. Tetapi itu mungkin tidak bekerja untuk orang lain dan mungkin berhenti bekerja kapan saja kompiler Anda memperbarui atau mungkin bahkan jika Anda hanya mengubah flag kompilasi.
François Andrieux

-3

Anda bisa menggunakan ini:

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

Ini hanya duplikat dari jawaban ini: stackoverflow.com/a/27832765/167958
Mahakuasa
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.