보호되거나 개인 생성자 만있는 클래스에서 :: std :: make_shared를 어떻게 호출합니까?


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. 그리고 "it"으로, this_is_private클래스를 언급하고 있는데, 이런 경우에는 이름을 바꿔야 할 것입니다. 나는 보통 constructor_access내 코드로 호출합니다 .
dalle

1
안타깝게도 생성자가 실제 매개 변수를 사용하면 작동하지 않습니다. 이 경우 {}유형 이름에 액세스하지 않고 개인 태그 를 전달할 수 있습니다 (g ++ 4.9.0으로 테스트). 실제 매개 변수 A가 없으면 이유를 모르고 실패하지만 {}에서 구성하려고 시도 합니다. this_is_private 생성자를 private으로 만들고 수정하는 정적 메서드를 제공하여 멤버 함수 서명에서 형식을 유출하지 않는 한 외부 에서이 메서드에 액세스 할 수있는 방법이 없어야한다고 생각합니다.
Stefan

3
Stefan, this_is_private개인 ctor 를 주면 A 급을 친구로 만들 수 있습니다. 허점을 닫는 것 같습니다.
Steven Kramer

78

std::make_shared20.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인터페이스 나 다형성 유형을 만들 필요가 없습니다 .

// 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이 concrete_A가 아닌 A를 삭제하지 않으므로 문제가 발생하지 않습니까?
Omnifarious

8
아, shared_ptr인스턴스화 할 때 삭제기를 저장 하기 때문에 삭제 make_shared자를 사용 하는 경우 절대적으로 올바른 유형을 사용해야합니다.
Omnifarious

1
@LucDanton Question은 제목에 개인 ctor를 요청하는 것처럼 인터페이스에 관한 것이 아닙니다. 또한, 그래서 나는이 질문에 어쨌든 있습니다. 전용 ctor와 raw 포인터를 반환하는 create 메서드가있는 machiavelli 클래스가있는 오래된 코드가 있으며 스마트 포인터로 변환하려고합니다.
zahir

2
나는이 접근법을 좋아하지만 (직접 사용하는) 가상 소멸자가 필요합니다. 인수가있는 생성자로 잘 확장됩니다 (통과 생성자를 제공하십시오). 그리고 개인 보다는 보호 를 사용하는 경우 헤더 사용자에게 완전히 보이지 않게 할 수 있습니다.
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는 로컬이 아니지만 개인 구조체였습니다 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

멋진 아이디어 모 히트가 저에게 많은 도움이되었습니다.
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

삭제 된 어설 션 및 복사 연산자는이를 금지합니다.
Dani

7
이것이 실제로 질문 한 것이 아니더라도 이것은 가장 간단한 접근법입니다. make_shared에는 몇 가지 좋은 특성이 있으며 가능한 한 어디에서나 사용하려고 시도하지만이 상황에서 make_shared의 런타임 성능 이점이 실제로 사용하는 데 필요한 추가 코드 복잡성 및 식보다 중요하지 않은 것 같습니다. make_shared의 성능이 정말로 필요하다면 미친 짓이지만, 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) template <typename ... Arg> .....와 같은 매크로를 사용합니다.
alpha

좋은 대답입니다. IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

이미 제공된 답변이 마음에 들지 않기 때문에 이전 답변만큼 일반적이지 않은 솔루션을 검색하기로 결정했지만 더 나은 솔루션을 찾았습니다 (tm). 돌이켜 보면 Omnifarius가 제공하는 것보다 훨씬 좋지는 않지만 그것을 좋아하는 다른 사람들이있을 수도 있습니다 :)

이것은 내가 발명 한 것이 아니라 Jonathan Wakely (GCC 개발자)의 아이디어입니다.

불행히도 std :: allocate_shared 구현의 작은 변화에 의존하기 때문에 모든 컴파일러에서 작동하지는 않습니다. 그러나이 변경 사항은 이제 표준 라이브러리에 대해 제안 된 업데이트이므로 향후 모든 컴파일러에서 지원 될 수 있습니다. 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의 솔루션을 사용할 수 없었습니다. 그러나 옴니 파리 우스의 것이 적용될 수 있습니다. 이전 예제에서 모든 사람이 MyAlloc을 사용하여 A 객체를 만들 수는 있지만 create () 메서드 외에 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는 다음을 제안합니다.

( 전체 스레드를 위해 여기 로 가십시오 )

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

이것이 표준에 대한 중요한 추가 사항처럼 들리면 연결된 isocpp Google 그룹에 2 센트를 추가하십시오.


1
나는 그것이 표준에 대한 좋은 추가라고 생각하지만 Google 그룹에 가입하고 의견을 작성한 다음 해당 그룹과 의견에주의를 기울이는 데 중요하지 않습니다. :-)
Omnifarious

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간단하고 휴대용 방법으로 위의 작업을 수행 할 수 있도록으로 볼 수있는 표준에.
보리스 달 스타 인

3

함께 작동하는 두 개의 엄격하게 관련된 클래스 A와 B가있을 때 발생하는 더 털이 있고 재미있는 문제가 있습니다.

A가 "마스터 클래스"이고 B가 "노예"라고 가정하십시오. B의 인스턴스화를 A로만 제한하려면 B의 생성자를 비공개로 설정하고 친구 B를 다음과 같이 A로 설정하십시오.

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

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

불행히도 std::make_shared<B>()의 메소드를 호출 A하면 컴파일러 B::B()가 비공개 에 대해 불평하게됩니다 .

이것에 대한 나의 해결책 은 개인 생성자가 있고 내부의 친구와 공유하고 생성자를 공개하고 인수와 같은 인수를 추가 하는 공개 Pass더미 클래스 (같은 nullptr_t)를 만드는 것입니다.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

인수를 취하는 구성자를 사용하려면 약간 도움이 될 수 있습니다.

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

마지막으로, 누군가 친구로서 사용될 때 clang이 Factory :: Type이 private 인 것에 대해 불평한다고 말한 경우, 공개적으로 공개하십시오. 그것을 노출시키는 것은 해가되지 않습니다.


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.
브라이언

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