Làm cách nào để gọi :: std :: make_ Shared trên một lớp chỉ có các hàm tạo được bảo vệ hoặc riêng tư?


187

Tôi có mã này không hoạt động, nhưng tôi nghĩ mục đích là rõ ràng:

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

Nhưng tôi gặp lỗi này khi biên dịch nó:

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

Thông báo này về cơ bản nói rằng một số phương thức ngẫu nhiên đi xuống trong ngăn xếp khởi tạo mẫu từ ::std::make_sharedkhông thể truy cập vào hàm tạo vì nó được bảo vệ.

Nhưng tôi thực sự muốn sử dụng cả hai ::std::make_sharedvà ngăn không cho bất kỳ ai tạo ra một đối tượng của lớp này mà không được chỉ ra bởi a ::std::shared_ptr. Có cách nào để thực hiện điều này?


Bạn có thể đánh dấu hàm sâu bên dưới cần nhà xây dựng là bạn, nhưng nó sẽ không khả dụng.
Dani

@Dani: Vâng, thật tuyệt khi có một giải pháp di động. Nhưng điều đó sẽ làm việc.
Omnifarious

Câu trả lời:


109

Câu trả lời này có lẽ tốt hơn, và câu trả lời tôi sẽ chấp nhận. Nhưng tôi cũng đã đưa ra một phương thức xấu hơn, nhưng vẫn để mọi thứ vẫn là nội tuyến và không yêu cầu lớp dẫn xuất:

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

Chỉnh sửa 2017-01-06: Tôi đã thay đổi điều này để làm rõ rằng ý tưởng này rõ ràng và đơn giản có thể mở rộng cho các nhà xây dựng có lập luận vì những người khác đang cung cấp câu trả lời dọc theo những dòng đó và có vẻ bối rối về điều này.


14
Thật ra, tôi là một fan hâm mộ lớn của những cấu trúc vô nghĩa đó chỉ được sử dụng làm chìa khóa . Tôi thích giải pháp này của Luc, nhưng đó có thể là biais của tôi chống lại sự kế thừa.
Matthieu M.

2
Đồng ý, tôi thích điều này tốt hơn là tốt.
ildjarn

3
@Berkus: Sau đó làm cho nó protectedthay vì private. Và bằng "nó", tôi đang đề cập đến this_is_privatelớp, có lẽ nên đổi tên trong trường hợp đó. Tôi thường gọi nó constructor_accesstrong mã của tôi.
dalle

1
Đáng buồn là điều này không hoạt động nếu nhà xây dựng của bạn có các tham số thực; trong trường hợp này, bạn có thể chỉ cần chuyển {}cho thẻ riêng mà không có quyền truy cập vào tên loại (được thử nghiệm với g ++ 4.9.0). Không có tham số thực, nó cố gắng xây dựng Atừ {}, mặc dù tôi không biết tại sao và thất bại. Tôi nghĩ làm cho hàm tạo này_is_private riêng tư và cung cấp một phương thức tĩnh để tạo nó sửa nó, vì sẽ không có cách nào để truy cập phương thức này từ bên ngoài trừ khi bạn rò rỉ kiểu chữ ký của hàm thành viên.
Stefan

3
Stefan, nếu bạn cho this_is_privatemột ctor riêng, bạn có thể biến lớp A thành một người bạn. Có vẻ để đóng lỗ hổng.
Steven Kramer

78

Xem xét các yêu cầu đối với std::make_sharedviệc tạo 20.7.2.2.6 shared_ptr [produc.smartptr. Shared.create], đoạn 1:

Yêu cầu: Biểu thức ::new (pv) T(std::forward<Args>(args)...), trong đó pvcó loại void*và các điểm để lưu trữ phù hợp để giữ một đối tượng của loại T, sẽ được hình thành tốt. Asẽ là một người cấp phát (17.6.3.5). Các constructor sao chép và hàm hủy của Asẽ không ném ngoại lệ.

Vì yêu cầu được quy định vô điều kiện theo biểu thức đó và những điều như phạm vi không được tính đến, tôi nghĩ rằng các thủ thuật như tình bạn là đúng đắn.

Một giải pháp đơn giản là xuất phát từ A. Điều này không cần phải tạo Agiao diện hoặc thậm chí là một loại đa hình.

// 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
Ồ, đó là một câu trả lời rất thông minh, và có thể tốt hơn một câu trả lời khác mà tôi đã nghĩ đến.
Omnifarious

Tuy nhiên, một câu hỏi sẽ không được chia sẻ_ptr xóa A và không cụ thể, và điều này có thể gây ra vấn đề không?
Omnifarious

8
À, đó là vì shared_ptrlưu trữ một deleter tại thời điểm khởi tạo và nếu bạn đang sử dụng make_shareddeleter thì nhất định phải sử dụng đúng loại.
Omnifarious

1
@LucDanton Câu hỏi không phải là về giao diện, vì tiêu đề cho thấy anh ta cũng đang yêu cầu một ctor riêng. Ngoài ra, đó là lý do tại sao tôi về câu hỏi này. Một số mã cũ có các lớp machiavelli, có một ctor riêng và một phương thức tạo trả về một con trỏ thô và tôi đang cố gắng chuyển đổi chúng thành các con trỏ thông minh.
zahir

2
Tôi thích cách tiếp cận này (sử dụng bản thân nó) nhưng bạn cần một hàm hủy ảo. Nó mở rộng tốt cho các hàm tạo với các đối số (chỉ cần cung cấp một hàm tạo vượt qua). Và nếu bạn đang sử dụng được bảo vệ chứ không phải riêng tư, bạn có thể làm cho nó hoàn toàn vô hình đối với người dùng của tiêu đề.
Joe Steele

69

Có thể là giải pháp đơn giản nhất. Dựa trên câu trả lời trước của Mohit Aron và kết hợp đề xuất của 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
nếu Acó các hàm tạo không mặc định, bạn cũng sẽ cần hiển thị chúng : struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Điều này làm cho tất cả các nhà xây dựng tư nhân Acó thể nhìn thấy như các nhà make_shared_enablerxây dựng. Sử dụng tính năng kế thừa của hàm tạo ( using A::A;) dường như không giúp ích ở đây vì các hàm tạo sẽ vẫn ở chế độ riêng tư.
anton_rh

2
@anton_rh: bạn không thể thêm đối số mẫu vào các lớp bên trong. Xem ở đây .
bobbel

3
Hừm ... Có vẻ như bạn đúng. Trong trường hợp của tôi, struct không phải là cục bộ, nhưng là một cấu trúc riêng : class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Xem ở đây cpp.sh/65qbr .
anton_rh

Điều này làm việc tuyệt vời. Có bất kỳ cơ hội nào để biến đây thành một tài sản thừa kế, vì vậy mô hình này không phải lặp đi lặp lại nhiều lần? Đặc biệt phiên bản trưng bày các hàm tạo không mặc định sẽ rất thú vị đối với tôi. Phiên bản mặc định sẽ "đơn thuần" yêu cầu một số cấu trúc cú pháp thay thế A bằng bất cứ thứ gì mà lớp kế thừa lớp. Tôi không biết bất cứ điều gì như vậy, nhưng tôi sẽ không ngạc nhiên khi biết rằng nó tồn tại ...
Kjeld Schmidt

30

Đây là một giải pháp gọn gàng cho việc này:

#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
Tôi thích điều này. Nó có thể được thực hiện đơn giản hơn một chút bằng cách xác định MakeSharedEnablercục bộ bên trong A::Create().
dlf

Ý tưởng tuyệt vời Mohit nó đã giúp tôi rất nhiều.
Jnana

12

Còn cái này thì sao?

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

13
Điều đó làm việc tuyệt vời. Nhưng ::std::make_sharedcó chức năng ở trên và ngoài việc đơn giản là chia sẻ_ptr thành một cái gì đó. Nó phân bổ số tham chiếu cùng với đối tượng để chúng nằm gần nhau. Tôi thực sự, thực sự muốn sử dụng ::std::make_shared.
Omnifarious

Các toán tử đã bị xóa và sao chép đã cấm điều này
Dani

7
Đây thực sự là cách tiếp cận đơn giản nhất, mặc dù nó không thực sự là câu hỏi đang hỏi. make_ Shared có một số đặc điểm tốt và tôi cố gắng sử dụng nó bất cứ khi nào có thể, nhưng trong tình huống này có vẻ như các lợi thế về hiệu suất thời gian chạy của make_ Shared không vượt quá độ phức tạp của mã và nghi lễ thực sự cần thiết để sử dụng nó. Nếu bạn thực sự cần hiệu năng của make_ Shared thì hãy phát điên, nhưng đừng bỏ qua sự đơn giản của việc chỉ sử dụng hàm tạo của shared_ptr.
Kevin

Hãy cẩn thận về rò rỉ bộ nhớ mặc dù ... hãy xem câu hỏi này 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_;
};

Điều này phần lớn giống như câu trả lời của Luc Danton, mặc dù biến nó thành một lớp địa phương là một liên lạc tốt đẹp. Một số giải thích kèm theo mã có thể làm cho câu trả lời này tốt hơn nhiều.

Thông thường, tôi muốn viết hàm nhỏ như vậy trong tệp tiêu đề nhưng không phải tệp cc. Thứ hai, trong thực tế, tôi sử dụng một macro trông giống như mẫu #define SharedPtrCreate (T) <tên chữ ... Arg> .....
alpha

Câu trả lời tốt. Tôi thậm chí đã đặt nó vào một macro có tên như IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

Vì tôi không thích câu trả lời đã được cung cấp, tôi quyết định tìm kiếm và tìm ra giải pháp không chung chung như câu trả lời trước nhưng tôi thích nó hơn (tm). Nhìn lại, nó không đẹp hơn nhiều so với Omnifarius cung cấp nhưng cũng có thể có những người khác cũng thích nó :)

Đây không phải là phát minh của tôi, nhưng đó là ý tưởng của Jonathan Wakely (nhà phát triển GCC).

Thật không may, nó không hoạt động với tất cả các trình biên dịch vì nó phụ thuộc vào một thay đổi nhỏ trong việc thực hiện std :: allocate_ Shared. Nhưng thay đổi này hiện là bản cập nhật được đề xuất cho các thư viện tiêu chuẩn, vì vậy nó có thể được hỗ trợ bởi tất cả các trình biên dịch trong tương lai. Nó hoạt động trên GCC 4.7.

Yêu cầu thay đổi nhóm làm việc thư viện tiêu chuẩn C ++ tại đây: http://lwg.github.com/issues/lwg-active.html#2070

Bản vá GCC với cách sử dụng ví dụ có tại đây: http://old.nabble.com/Re%3A--v3--Imcellence-pulum_traits-and-allocator_traits-p31723738.html

Giải pháp hoạt động dựa trên ý tưởng sử dụng std :: allocate_ Shared (thay vì std :: make_ Shared) với một cấp phát tùy chỉnh được khai báo là bạn với lớp với hàm tạo riêng.

Ví dụ từ OP sẽ như thế này:

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

Một ví dụ phức tạp hơn dựa trên tiện ích tôi đang làm việc. Với điều này, tôi không thể sử dụng giải pháp của Luc. Nhưng một trong những Omnifarius có thể được điều chỉnh. Không phải là trong ví dụ trước, mọi người đều có thể tạo một đối tượng A bằng cách sử dụng MyAlloc trong trường hợp này, không có cách nào để tạo A hoặc B ngoài phương thức tạo ().

#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

Lý tưởng nhất, tôi nghĩ rằng giải pháp hoàn hảo sẽ yêu cầu bổ sung cho tiêu chuẩn C ++. Andrew Schepler đề xuất như sau:

(Tới đây cho toàn bộ chủ đề)

chúng ta có thể mượn một ý tưởng từ boost :: iterator_core_access. Tôi đề xuất một lớp học mớistd::shared_ptr_access không có thành viên nào được bảo vệ hoặc công khai và để chỉ định rằng cho std :: make_spl (args ...) và std :: alloc_ Shared (a, args ...), các biểu thức :: new (pv) T (chuyển tiếp (args) ...) và ptr-> ~ T () phải được định dạng tốt trong ngữ cảnh của std :: shared_ptr_access.

Việc triển khai std :: shared_ptr_access có thể giống như:

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

Sử dụng

Nếu / khi những điều trên được thêm vào tiêu chuẩn, chúng ta chỉ cần làm:

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

Nếu điều này cũng có vẻ như là một bổ sung quan trọng cho tiêu chuẩn đối với bạn, vui lòng thêm 2 xu của bạn vào Nhóm Google isocpp được liên kết.


1
Tôi nghĩ rằng đó là một bổ sung tốt cho tiêu chuẩn, nhưng nó không đủ quan trọng để tôi dành thời gian tham gia Nhóm Google và nhận xét và sau đó chú ý đến nhóm đó và nhận xét. :-)
Omnifarious

4

Tôi nhận ra chủ đề này khá cũ, nhưng tôi đã tìm thấy một câu trả lời không yêu cầu sự kế thừa hoặc các đối số bổ sung cho hàm tạo mà tôi không thể thấy ở nơi nào khác. Nó không phải là di động mặc dù:

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

Tôi đã thử nghiệm trên windows và linux, nó có thể cần tinh chỉnh cho các nền tảng khác nhau.


1
Tôi bị cám dỗ -1 vì thiếu tính di động. Các câu trả lời khác (đặc biệt là câu trả lời của 'lớp chính') khá thanh lịch và câu trả lời không mang tính di động rất xấu. Tôi không thể nghĩ ra lý do bạn sử dụng câu trả lời không di động. Nó không nhanh hơn hay bất cứ thứ gì như thế.
Omnifarious

@Omnifarious Nó thực sự không di động và tôi không khuyến nghị, nhưng tôi tin rằng đây thực sự là giải pháp đúng đắn nhất về mặt ngữ nghĩa. Trong câu trả lời của tôi , tôi liên kết đến một đề xuất thêm std::shared_ptr_accessvào tiêu chuẩn, có thể được xem là cho phép thực hiện các điều trên một cách đơn giản và di động.
Boris Dalstein

3

Có một vấn đề thú vị hơn và thú vị hơn xảy ra khi bạn có hai lớp A và B liên quan chặt chẽ với nhau.

Nói A là "lớp chủ" và B là "nô lệ" của nó. Nếu bạn muốn hạn chế khởi tạo B chỉ với A, bạn sẽ đặt công cụ xây dựng của B ở chế độ riêng tư và bạn B thành A như thế này

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

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

Thật không may, gọi std::make_shared<B>()từ một phương thức Asẽ làm cho trình biên dịch phàn nàn về B::B()việc riêng tư.

Giải pháp của tôi cho vấn đề này là tạo một Passlớp giả công khai (giống như nullptr_t) bên trong Bcó hàm tạo riêng và là bạn với Avà làm cho hàm tạo Bcủa công khai và thêm Passvào các đối số của nó, như thế này.

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

Nếu bạn cũng muốn kích hoạt một bộ dẫn có các đối số, điều này có thể giúp một chút.

#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

[Chỉnh sửa] Tôi đọc qua các chủ đề được ghi chú ở trên về một std::shared_ptr_access<>đề xuất được tiêu chuẩn hóa . Trong đó có một phản hồi lưu ý một bản sửa lỗi std::allocate_shared<>và một ví dụ về việc sử dụng nó. Tôi đã điều chỉnh nó theo mẫu nhà máy bên dưới và đã thử nghiệm nó theo gcc C ++ 11/14/17. Nó cũng hoạt động với std::enable_shared_from_this<>, vì vậy rõ ràng sẽ thích hợp hơn với giải pháp ban đầu của tôi trong câu trả lời này. Đây là ...

#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] Tôi tìm thấy một giải pháp bằng cách sử dụng hàm tạo bí danh con trỏ chia sẻ. Nó cho phép cả ctor và dtor ở chế độ riêng tư, cũng như sử dụng bộ xác định cuối cùng.

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

Lưu ý rằng cách tiếp cận ở trên không phù hợp với std::enable_shared_from_this<>vì ban đầu std::shared_ptr<>là về trình bao bọc chứ không phải kiểu chính nó. Chúng tôi có thể giải quyết vấn đề này với một lớp tương đương tương thích với nhà máy ...

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

Cuối cùng, ai đó nói clang phàn nàn về Factory :: Loại là riêng tư khi được sử dụng như một người bạn, vì vậy hãy công khai nếu đó là trường hợp. Phơi bày nó không có hại.


3

Tôi đã có cùng một vấn đề, nhưng không có câu trả lời nào hiện có thực sự thỏa đáng vì tôi cần chuyển các đối số cho hàm tạo được bảo vệ. Hơn nữa, tôi cần phải làm điều này cho một số lớp, mỗi lớp lấy các đối số khác nhau.

Để đạt được hiệu quả đó và dựa trên một số câu trả lời hiện có mà tất cả đều sử dụng các phương pháp tương tự, tôi trình bày cái nugget nhỏ này:

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

Nguyên nhân của vấn đề là nếu hàm hoặc lớp mà bạn của bạn thực hiện các cuộc gọi cấp thấp hơn đến hàm tạo của bạn, thì chúng cũng phải được kết bạn. std :: make_ Shared không phải là chức năng thực sự gọi hàm tạo của bạn để kết bạn với nó không có gì khác biệt.

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 thực sự đang gọi nhà xây dựng của bạn, vì vậy nó cần phải là một người bạn. Vì đó là một chút tối nghĩa, tôi sử dụng một macro

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

Sau đó, khai báo lớp của bạn trông khá đơn giản. Bạn có thể tạo một macro duy nhất để khai báo ptr và lớp nếu bạn thích.

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

Đây thực sự là một vấn đề quan trọng. Để làm cho mã duy trì, di động, bạn cần ẩn càng nhiều việc thực hiện càng tốt.

typedef std::shared_ptr<A> APtr;

ẩn cách bạn xử lý con trỏ thông minh của mình một chút, bạn phải chắc chắn sử dụng typedef của mình. Nhưng nếu bạn luôn phải tạo một cái bằng cách sử dụng make_ Shared, nó sẽ đánh bại mục đích.

Ví dụ trên buộc mã sử dụng lớp của bạn sử dụng hàm tạo con trỏ thông minh của bạn, điều đó có nghĩa là nếu bạn chuyển sang một hương vị mới của con trỏ thông minh, bạn thay đổi khai báo lớp và bạn có cơ hội hoàn thành tốt. KHÔNG cho rằng ông chủ hoặc dự án tiếp theo của bạn sẽ sử dụng kế hoạch stl, boost, vv để thay đổi nó một ngày nào đó.

Làm điều này trong gần 30 năm, tôi đã phải trả giá đắt về thời gian, nỗi đau và tác dụng phụ để sửa chữa điều này khi nó được thực hiện sai cách đây nhiều năm.


2
std::_Ref_count_objlà một chi tiết thực hiện. Điều đó có nghĩa là trong khi giải pháp này có thể phù hợp với bạn, hiện tại, trên nền tảng của bạn. Nhưng nó có thể không hoạt động cho người khác và có thể ngừng hoạt động bất cứ khi nào trình biên dịch của bạn cập nhật hoặc có thể ngay cả khi bạn chỉ thay đổi cờ biên dịch.
François Andrieux

-3

Bạn có thể sử dụng điều này:

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
Không sử dụng 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;
}

Đây chỉ là một bản sao của câu trả lời này: stackoverflow.com/a/27832765/167958
Omnifarious
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.