ฉันจะโทร :: std :: make_shared ในคลาสที่มีเพียง constructor ที่ได้รับการป้องกันหรือส่วนตัวได้อย่างไร


187

ฉันมีรหัสนี้ใช้งานไม่ได้ แต่ฉันคิดว่าเจตนาชัดเจน:

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

แต่ฉันได้รับข้อผิดพลาดนี้เมื่อรวบรวม:

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

ข้อความนี้โดยทั่วไปบอกว่าวิธีการสุ่มบางอย่างลดลงในแม่แบบการสร้างอินสแตนซ์จาก::std::make_sharedไม่สามารถเข้าถึงตัวสร้างได้เนื่องจากมันได้รับการป้องกัน

แต่ผมอยากจะใช้ทั้งสองและป้องกันไม่ให้ใครจากการทำวัตถุของชั้นนี้ที่ไม่ได้ชี้ไปที่โดยหนึ่ง::std::make_shared ::std::shared_ptrมีวิธีใดบ้างที่จะทำให้สิ่งนี้สำเร็จ?


คุณสามารถทำเครื่องหมายฟังก์ชั่นลึกลงที่ต้องการตัวสร้างเป็นเพื่อน แต่นั่นจะไม่สามารถพกพาได้
Dani

@Dani: ใช่มันคงจะดีถ้ามีโซลูชั่นพกพา แต่นั่นจะได้ผล
Omnifarious

คำตอบ:


109

คำตอบนี้น่าจะดีกว่าและคำตอบก็น่าจะยอมรับ แต่ฉันก็คิดวิธีที่น่าเกลียด แต่ก็ยังให้ทุกอย่างยังอยู่ในบรรทัดและไม่ต้องการคลาสที่ได้รับ:

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

แก้ไข 2017-01-06:ฉันเปลี่ยนสิ่งนี้เพื่อให้ชัดเจนว่าความคิดนี้ชัดเจนและขยายได้ง่ายสำหรับผู้สร้างที่โต้แย้งเพราะคนอื่น ๆ ให้คำตอบตามแนวเหล่านั้นและดูเหมือนสับสนเกี่ยวกับเรื่องนี้


14
จริงๆแล้วฉันเป็นแฟนตัวยงของโครงสร้างที่ไร้ความหมายเหล่านั้นใช้เป็นกุญแจเท่านั้น ฉันชอบสิ่งนี้กับโซลูชันของ Luc แต่นั่นอาจเป็นเบียริสของฉันกับการสืบทอด
Matthieu M.

2
ตกลงฉันชอบสิ่งนี้ดีกว่าเช่นกัน
ildjarn

3
@Berkus: แล้วทำให้มันแทนprotected privateและโดย "มัน" ฉันหมายถึงthis_is_privateชั้นเรียนซึ่งอาจจะเปลี่ยนชื่อในกรณีเช่นนี้ ฉันมักจะเรียกมันว่าเป็นconstructor_accessรหัสของฉัน
dalle

1
น่าเสียดายที่นี่ไม่ได้ผลหากคอนสตรัคเตอร์ของคุณใช้พารามิเตอร์จริง ในกรณีนี้คุณสามารถส่ง{}แท็กส่วนตัวโดยไม่ต้องเข้าถึงชื่อประเภท (ทดสอบด้วย g ++ 4.9.0) หากไม่มีพารามิเตอร์จริงมันจะพยายามสร้างAจาก {} แม้ว่าฉันจะไม่ทราบสาเหตุและล้มเหลว ฉันคิดว่าการสร้าง this_is_private constructor เป็นส่วนตัวและจัดให้มีวิธีการแบบคงที่เพื่อสร้างมันขึ้นมาแก้ไขเนื่องจากไม่มีวิธีการเข้าถึงวิธีการนี้จากภายนอกเว้นแต่คุณจะรั่วประเภทในลายเซ็นฟังก์ชั่นสมาชิก
Stefan

3
สเตฟานถ้าคุณให้this_is_privatector ส่วนตัวคุณสามารถทำให้เพื่อนในชั้นเรียน ดูเหมือนว่าจะปิดช่องโหว่
Steven Kramer

78

ดูข้อกำหนดสำหรับstd::make_sharedใน 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], ย่อหน้า 1:

ต้องการ:การแสดงออก::new (pv) T(std::forward<Args>(args)...)ที่pvมีประเภทvoid*และจุดในการจัดเก็บที่เหมาะสมที่จะถือวัตถุประเภทTจะต้องมีรูปแบบที่ดี Aจะต้องเป็นตัวจัดสรร (17.6.3.5) ตัวสร้างสำเนาและตัวทำลายของAจะไม่ส่งข้อยกเว้น

เนื่องจากข้อกำหนดถูกระบุไว้อย่างไม่มีเงื่อนไขในแง่ของการแสดงออกและสิ่งต่าง ๆ เช่นขอบเขตที่ไม่ได้นำมาพิจารณาผมจึงคิดว่าเทคนิคเช่นมิตรภาพนั้นหมดไปแล้ว

Aวิธีง่ายๆคือการเป็นผลมาจาก สิ่งนี้ไม่จำเป็นต้องมีการสร้างAส่วนต่อประสานหรือแม้กระทั่งประเภท polymorphic

// 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
โอ้นั่นเป็นคำตอบที่ฉลาดมากและอาจดีกว่าที่ฉันคิดไว้
Omnifarious

แม้ว่าจะมีคำถามหนึ่งข้อ Shared_ptr จะไม่ลบ A และไม่ใช่ Concrete_A และสิ่งนี้จะทำให้เกิดปัญหาหรือไม่
Omnifarious

8
อ๊ะเป็นเพราะการจัดshared_ptrเก็บ deleter ในช่วงเวลาของการเริ่มต้นและถ้าคุณกำลังใช้make_shareddeleter อย่างแน่นอนจะต้องใช้ประเภทที่เหมาะสม
Omnifarious

1
@LucDanton คำถามไม่ได้เกี่ยวกับอินเทอร์เฟซเนื่องจากชื่อเรื่องแสดงให้เห็นว่าเขายังขอ ctor ส่วนตัวด้วย นอกจากนี้นั่นคือเหตุผลที่ฉันกำลังถามคำถามนี้อยู่แล้ว โค้ดเก่าบางตัวมีคลาส machiavelli ซึ่งมี ctor ส่วนตัวและวิธีการสร้างที่ส่งคืนตัวชี้แบบดิบและฉันพยายามแปลงให้เป็นพอยน์เตอร์อัจฉริยะ
ฮีร์

2
ฉันชอบวิธีนี้ (ใช้ด้วยตัวเอง) แต่คุณต้องมี destructor เสมือน มันขยายไปถึงคอนสตรัคเตอร์ที่มีอาร์กิวเมนต์ (ให้เป็นตัวสร้าง passthrough) และถ้าคุณใช้การป้องกันมากกว่าส่วนตัวคุณสามารถทำให้ผู้ใช้ส่วนหัวมองไม่เห็น
Joe Steele

69

อาจเป็นทางออกที่ง่ายที่สุด ตามคำตอบก่อนหน้าโดย Mohit Aron และรวมคำแนะนำของ 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
หากมีการก่อสร้างที่ไม่ได้เริ่มต้นที่คุณยังจะต้องให้พวกเขา:A struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };สิ่งนี้ทำให้คอนสตรัคส่วนตัวทั้งหมดAมองเห็นได้เป็นคอนmake_shared_enablerสตรัคเตอร์ การใช้คุณสมบัติการสืบทอดคอนสตรัคเตอร์ ( using A::A;) ดูเหมือนว่าไม่ได้ช่วยที่นี่เพราะคอนสตรัคจะยังคงเป็นส่วนตัว
anton_rh

2
@anton_rh: คุณไม่สามารถเพิ่มอาร์กิวเมนต์เทมเพลตในคลาสภายในได้ ดูที่นี่
bobbel

3
หืม ... ดูเหมือนว่าคุณพูดถูก ในกรณีของฉัน struct ไม่ได้ในประเทศ แต่เป็น struct class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }ส่วนตัว: ดูที่นี่cpp.sh/65qbr
anton_rh

มันใช้งานได้ดี มีโอกาสที่จะทำให้ทรัพย์สินนี้เป็นสิ่งที่สืบทอดได้หรือไม่ดังนั้นรูปแบบนี้จึงไม่จำเป็นต้องทำซ้ำหลายครั้ง? โดยเฉพาะรุ่นที่แสดงคอนสตรัคเตอร์ที่ไม่ใช่ค่าเริ่มต้นจะน่าสนใจสำหรับฉันมาก รุ่นเริ่มต้นจะ "เพียงแค่" ต้องมีโครงสร้างประโยคที่แทนที่ A ด้วยคลาสใดก็ตามที่สืบทอดคลาสนั้น ฉันไม่รู้อะไรเลย แต่ฉันไม่แปลกใจที่รู้ว่ามันมีอยู่จริง
Kjeld Schmidt

30

นี่เป็นวิธีที่เรียบร้อยสำหรับสิ่งนี้:

#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
ฉันชอบสิ่งนี้. มันสามารถทำให้ง่ายขึ้นเล็กน้อยโดยการกำหนดMakeSharedEnablerภายในA::Create()เครื่อง
dlf

ไอเดียที่ยอดเยี่ยม Mohit มันช่วยฉันได้มาก
Jnana

12

แล้วเรื่องนี้ล่ะ

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

13
มันใช้งานได้ดีมาก แต่::std::make_sharedมีฟังก์ชั่นการทำงานที่เหนือกว่าเพียงแค่ทำ shared_ptr ให้กับบางสิ่ง มันจัดสรรจำนวนการอ้างอิงพร้อมกับวัตถุดังนั้นพวกมันจึงอยู่ใกล้กัน ฉันต้องการใช้::std::make_sharedจริงๆ
Omnifarious

การลบ assigment และ copy โอเปอเรเตอร์ต้องห้าม
Dani

7
นี่เป็นวิธีที่ตรงไปตรงมาที่สุดแม้ว่าจะไม่ใช่คำถามที่ถาม make_shared มีลักษณะที่ดีและฉันพยายามใช้ทุกที่ที่เป็นไปได้ แต่ในสถานการณ์นี้ดูเหมือนว่าข้อดีของประสิทธิภาพการใช้งานของ make_shared นั้นไม่ได้เกินความซับซ้อนของรหัสพิเศษและพิธีที่ต้องใช้จริง หากคุณต้องการประสิทธิภาพของ make_shared จริงๆแล้วก็บ้าไปแล้ว แต่อย่ามองข้ามความเรียบง่ายของการใช้ constructor ของ shared_ptr
Kevin

ระวังเรื่องการรั่วไหลของหน่วยความจำ ... ดูคำถามนี้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_;
};

นี่เป็นสิ่งเดียวกับคำตอบของ Luc Danton แม้ว่าการเปลี่ยนเป็นคลาสท้องถิ่นนั้นเป็นสิ่งที่ดี คำอธิบายบางอย่างเกี่ยวกับรหัสอาจทำให้คำตอบนี้ดีขึ้นมาก

โดยปกติฉันต้องการเขียนฟังก์ชั่นเล็ก ๆ เช่นนี้ในไฟล์ส่วนหัว แต่ไม่ใช่ไฟล์ cc ประการที่สองในทางปฏิบัติฉันใช้แมโครซึ่งดูเหมือน #define SharedPtrCreate (T) แม่แบบ <typename ... Arg> .....
alpha

คำตอบที่ดี. ฉันยังใส่มันลงในมาโครที่เรียกว่า IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

เนื่องจากฉันไม่ชอบคำตอบที่ได้ให้ไปแล้วฉันจึงตัดสินใจค้นหาและพบวิธีแก้ปัญหาที่ไม่ธรรมดาเหมือนคำตอบก่อนหน้า แต่ฉันชอบดีกว่า (tm) เมื่อมองย้อนกลับไปมันไม่ได้มีอะไรดีไปกว่า Omnifarius ที่ให้บริการ แต่อาจมีคนที่ชอบด้วยเช่นกัน :)

ฉันไม่ได้เป็นผู้คิดค้น แต่เป็นความคิดของ Jonathan Wakely (นักพัฒนา GCC)

น่าเสียดายที่มันไม่สามารถใช้งานได้กับคอมไพเลอร์ทั้งหมดเพราะมันขึ้นอยู่กับการเปลี่ยนแปลงเล็กน้อยใน std :: allocate_shared Implementation แต่ตอนนี้การเปลี่ยนแปลงนี้เป็นการปรับปรุงที่เสนอสำหรับไลบรารีมาตรฐานดังนั้นจึงอาจได้รับการสนับสนุนจากคอมไพเลอร์ทั้งหมดในอนาคต มันทำงานบน GCC 4.7

คำขอเปลี่ยนแปลงกลุ่มทำงานของไลบรารีมาตรฐาน C ++ อยู่ที่นี่: http://lwg.github.com/issues/lwg-active.html#2070

แพทช์ GCC พร้อมการใช้งานตัวอย่างอยู่ที่นี่: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

โซลูชันทำงานบนแนวคิดเพื่อใช้ std :: allocate_shared (แทนที่จะเป็น std :: make_shared) กับตัวจัดสรรที่กำหนดเองที่ประกาศเพื่อนกับคลาสด้วยตัวสร้างส่วนตัว

ตัวอย่างจาก OP จะมีลักษณะเช่นนี้:

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

ตัวอย่างที่ซับซ้อนมากขึ้นซึ่งเป็นไปตามยูทิลิตี้ที่ฉันกำลังทำอยู่ ด้วยวิธีนี้ฉันไม่สามารถใช้โซลูชันของ Luc ได้ แต่ Omnifarius สามารถดัดแปลงได้ ไม่ใช่ว่าในตัวอย่างก่อนหน้าทุกคนสามารถสร้างวัตถุ A โดยใช้ MyAlloc ในสิ่งนี้ไม่มีวิธีการสร้าง A หรือ B นอกเหนือจากวิธีการสร้าง ()

#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

เป็นการดีที่ฉันคิดว่าโซลูชันที่สมบูรณ์แบบจะต้องมีการเพิ่มเติมในมาตรฐาน C ++ Andrew Schepler เสนอสิ่งต่อไปนี้:

(ไปที่นี่เพื่อกระทู้ทั้งหมด)

เราสามารถยืมความคิดจากการเพิ่ม :: iterator_core_access ฉันเสนอคลาสใหม่std::shared_ptr_accessไม่มีสมาชิกสาธารณะหรือได้รับการคุ้มครองและเพื่อระบุว่าสำหรับ std :: make_shared (args ... ) และ std :: alloc_shared (a, args ... ), นิพจน์ :: new (pv) T (forward (args) ... ) และ ptr-> ~ T () ต้องมีรูปแบบที่ถูกต้องในบริบทของ std :: shared_ptr_access

การใช้ std :: shared_ptr_access อาจมีลักษณะดังนี้:

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

การใช้

หาก / เมื่อมีการเพิ่มข้างต้นในมาตรฐานเราก็จะทำ:

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

หากสิ่งนี้ดูเหมือนจะเป็นการเพิ่มมาตรฐานที่สำคัญให้กับคุณคุณสามารถเพิ่ม 2 เซนต์ของคุณไปยัง isocpp Google Group ที่เชื่อมโยง


1
ฉันคิดว่ามันเป็นการเพิ่มมาตรฐานที่ดี แต่ก็ไม่สำคัญพอที่ฉันจะใช้เวลาในการเข้าร่วม Google Group และแสดงความคิดเห็นจากนั้นให้ความสนใจกับกลุ่มนั้นและความคิดเห็น :-)
รอบรู้

4

ฉันรู้ว่าเธรดนี้ค่อนข้างเก่า แต่ฉันพบคำตอบที่ไม่ต้องการการสืบทอดหรือการขัดแย้งเพิ่มเติมกับตัวสร้างที่ฉันไม่เห็นที่อื่น มันไม่ได้พกพาแม้ว่า:

#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 และ linux แล้วอาจต้องปรับแต่งสำหรับแพลตฟอร์มที่แตกต่างกัน


1
ฉันอยากจะ -1 เพราะขาดการพกพา คำตอบอื่น ๆ (โดยเฉพาะคำตอบ 'คีย์คลาส') นั้นค่อนข้างหรูหราและคำตอบที่ไม่ใช่แบบพกพาน่าเกลียดมาก ฉันไม่สามารถนึกถึงเหตุผลที่คุณใช้คำตอบที่ไม่ใช่แบบพกพา มันไม่เร็วหรืออะไรอย่างนั้น
Omnifarious

@ Omnifarious แน่นอนว่ามันไม่สามารถพกพาได้และฉันจะไม่แนะนำ แต่ฉันเชื่อว่านี่เป็นวิธีการแก้ปัญหาที่ถูกต้องที่สุด ในคำตอบของฉันฉันเชื่อมโยงไปยังข้อเสนอของการเพิ่มstd::shared_ptr_accessมาตรฐานซึ่งอาจถูกมองว่าเป็นการอนุญาตให้ทำข้างต้นในวิธีที่ง่ายและพกพา
Boris Dalstein

3

มีปัญหาที่น่าขนลุกและน่าสนใจมากขึ้นที่เกิดขึ้นเมื่อคุณมีคลาส A และ B ที่เกี่ยวข้องกันสองคลาสที่ทำงานร่วมกันอย่างเคร่งครัด

พูดว่า A คือ "เจ้านายชั้นสูง" และ B "ทาส" ของมัน หากคุณต้องการ จำกัด การสร้างอินสแตนซ์ของ B เพียง A คุณจะทำให้คอนสตรัคเตอร์ของ B เป็นแบบส่วนตัวและเพื่อน B เป็นแบบนี้

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

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

น่าเสียดายที่การโทรstd::make_shared<B>()จากเมธอดAจะทำให้คอมไพเลอร์บ่นเกี่ยวกับB::B()ความเป็นส่วนตัว

วิธีแก้ปัญหาของฉันคือการสร้างPassคลาสหุ่นสาธารณะ(เหมือนnullptr_t) ภายในBที่มีคอนสตรัคเตอร์ส่วนตัวและเป็นเพื่อนกับAและทำให้คอนBสตรัคเตอร์สาธารณะและเพิ่มPassการขัดแย้งเช่นนี้

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

หากคุณต้องการเปิดใช้งาน constuctor ที่รับอาร์กิวเมนต์มันอาจช่วยได้บ้าง

#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

[แก้ไข] ฉันอ่านหัวข้อที่กล่าวถึงข้างต้นตามstd::shared_ptr_access<>ข้อเสนอมาตรฐาน ภายในมีการตอบสนองที่สังเกตเห็นการแก้ไขstd::allocate_shared<>และตัวอย่างของการใช้งาน ฉันได้ดัดแปลงเป็นเทมเพลตโรงงานด้านล่างและทดสอบภายใต้ gcc C ++ 11/14/17 มันใช้งานได้std::enable_shared_from_this<>ดีเช่นกันดังนั้นจะเห็นได้ชัดว่าเหมาะกับโซลูชันดั้งเดิมของฉันในคำตอบนี้ ที่นี่มันคือ ...

#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] ฉันพบวิธีแก้ปัญหาโดยใช้ตัวสร้างสมนามตัวชี้ที่ใช้ร่วมกัน อนุญาตให้ทั้ง ctor และ dtor เป็นส่วนตัวเช่นเดียวกับการใช้ตัวระบุสุดท้าย

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

โปรดทราบว่าวิธีการด้านบนไม่สามารถเล่นได้ดีstd::enable_shared_from_this<>เนื่องจากการเริ่มต้นใช้std::shared_ptr<>กับตัวตัดและไม่ใช่ตัวพิมพ์เอง เราสามารถจัดการกับคลาสที่เทียบเท่าที่เข้ากันได้กับโรงงาน ...

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

สุดท้ายมีคนบอกว่าเสียงดังกังวานบ่นเกี่ยวกับ Factory :: ประเภทความเป็นส่วนตัวเมื่อใช้เป็นเพื่อนดังนั้นให้เปิดเผยถ้าเป็นกรณีนี้ การเปิดเผยว่าไม่มีอันตรายใด ๆ


3

ฉันมีปัญหาเดียวกัน แต่ไม่มีคำตอบใดที่น่าพอใจจริง ๆ เพราะฉันต้องการส่งข้อโต้แย้งไปยังตัวสร้างที่ได้รับการป้องกัน ยิ่งกว่านั้นฉันต้องทำสิ่งนี้สำหรับหลาย ๆ คลาสโดยแต่ละข้อโต้แย้งต่างกัน

ในการสร้างเอฟเฟกต์นั้นและการสร้างคำตอบที่มีอยู่หลายข้อซึ่งทุกวิธีใช้วิธีที่คล้ายกันฉันขอนำเสนอนักเก็ตตัวน้อย:

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

สาเหตุของปัญหาคือถ้าฟังก์ชั่นหรือคลาสที่เพื่อนคุณโทรระดับล่างไปที่คอนสตรัคเตอร์ของคุณพวกเขาจะต้องเป็นเพื่อนด้วย std :: make_shared ไม่ใช่ฟังก์ชั่นที่เรียกว่านวกรรมิกของคุณ

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 กำลังเรียกคอนสตรัคเตอร์ของคุณดังนั้นมันต้องเป็นเพื่อน เนื่องจากมันค่อนข้างคลุมเครือฉันจึงใช้มาโคร

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

การประกาศคลาสของคุณนั้นค่อนข้างง่าย คุณสามารถสร้างมาโครเดียวเพื่อประกาศค่า ptr และคลาสได้หากคุณต้องการ

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

นี่เป็นปัญหาที่สำคัญจริงๆ ในการบำรุงรักษาโค้ดแบบพกพาคุณต้องซ่อนการใช้งานให้มากที่สุดเท่าที่จะทำได้

typedef std::shared_ptr<A> APtr;

ซ่อนวิธีการจัดการตัวชี้สมาร์ทของคุณเล็กน้อยคุณต้องแน่ใจว่าใช้ typedef ของคุณ แต่ถ้าคุณต้องสร้างมันขึ้นมาโดยใช้ make_shared ก็จะต้องเสียจุดประสงค์

ตัวอย่างข้างต้นบังคับให้ใช้รหัสคลาสของคุณเพื่อใช้ตัวสร้างสมาร์ทพอยน์เตอร์ของคุณซึ่งหมายความว่าหากคุณเปลี่ยนไปใช้สมาร์ทพ้อยท์ใหม่คุณจะเปลี่ยนการประกาศคลาสของคุณและคุณมีโอกาสที่จะเสร็จ อย่าสันนิษฐานว่าเจ้านายหรือโครงการต่อไปของคุณจะใช้ stl, boost และวางแผนอื่น ๆ สำหรับการเปลี่ยนแปลงในบางวัน

การทำเช่นนี้เป็นเวลาเกือบ 30 ปีฉันได้จ่ายเงินจำนวนมากในเวลาความเจ็บปวดและผลข้างเคียงเพื่อซ่อมแซมสิ่งนี้เมื่อมันทำผิดเมื่อหลายปีก่อน


2
std::_Ref_count_objคือรายละเอียดการนำไปปฏิบัติ นั่นหมายความว่าในขณะที่โซลูชันนี้อาจใช้งานได้สำหรับคุณในตอนนี้บนแพลตฟอร์มของคุณ แต่มันอาจไม่ทำงานสำหรับคนอื่นและอาจหยุดทำงานเมื่อใดก็ตามที่คอมไพเลอร์ของคุณอัปเดตหรือแม้กระทั่งถ้าคุณเพียงแค่เปลี่ยนสถานะการรวบรวม
François Andrieux

-3

คุณสามารถใช้สิ่งนี้:

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

นี่เป็นเพียงคำตอบที่ซ้ำกัน: stackoverflow.com/a/27832765/167958
Omnifarious
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.