개발/C++

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

MinorMan 2020. 9. 30. 22:35
반응형
C:\Anaconda3\envs\YYcompany\lib\site-packages\pymysql\cursors.py:329: Warning: (1265, "Data truncated for column 'original_url' at row 1") self._do_get_result()

<질문>

작동하지 않는 코드가 있지만 의도는 분명하다고 생각합니다.

testmakeshared.cpp

#include 

class A {
 public:
   static ::std::shared_ptr create() {
      return ::std::make_shared();
   }

 protected:
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr 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, __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, _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, _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, _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, _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이 가리 키지 않는이 클래스의 객체를 만드는 것을 막고 싶습니다. 이를 수행 할 방법이 있습니까?


<답변1>

이 대답은 아마도 더 좋을 것이고 내가 받아 들일 것입니다. 그러나 나는 또한 더 추악한 방법을 생각해 냈지만 여전히 모든 것을 인라인으로 유지하고 파생 클래스가 필요하지 않습니다.

#include 
#include 

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template 
   static ::std::shared_ptr create(T &&...args) {
      return ::std::make_shared(this_is_private{0},
                                   ::std::forward(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr foo()
{
   return A::create();
}

::std::shared_ptr bar()
{
   return A::create("George", 5);
}

::std::shared_ptr errors()
{
   ::std::shared_ptr 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 : 다른 사람들이 그 라인을 따라 답변을 제공하고 이에 대해 혼란스러워 보였기 때문에이 아이디어가 인수를 취하는 생성자에게 명확하고 간단하게 확장 될 수 있음을 분명히하기 위해 이것을 변경했습니다.


<답변2>

20.7.2.2.6 shared_ptr 생성 [util.smartptr.shared.create], 단락 1의 std :: make_shared에 대한 요구 사항을 살펴 봅니다.

요구 사항 : :: new (pv) T (std :: forward (args) ...), 여기서 pv는 void * 유형을 갖고 T 유형의 물체를 보관하기에 적합한 저장소를 가리 킵니다. A는 할당 자 (17.6.3.5)입니다. A의 복사 생성자와 소멸자는 예외를 발생시키지 않습니다.

요구 사항은 그 표현에 대해 무조건 지정되고 범위와 같은 것은 고려되지 않았기 때문에 우정과 같은 트릭은 옳다고 생각합니다.

간단한 해결책은 A에서 파생하는 것입니다.이를 위해 A를 인터페이스 또는 다형성 유형으로 만들 필요가 없습니다.

// interface in header
std::shared_ptr make_a();

// implementation in source
namespace {

struct concrete_A: public A {};

} // namespace

std::shared_ptr
make_a()
{
    return std::make_shared();
}

<답변3>

아마도 가장 간단한 해결책 일 것입니다. Mohit Aron의 이전 답변을 기반으로 dlf의 제안을 통합했습니다.

#include 

class A
{
public:
    static std::shared_ptr create()
    {
        struct make_shared_enabler : public A {};

        return std::make_shared();
    }

private:
    A() {}  
};

<답변4>

여기에 대한 깔끔한 솔루션이 있습니다.

#include 

class A {
   public:
     static shared_ptr Create();

   private:
     A() {}

     struct MakeSharedEnabler;   
 };

struct A::MakeSharedEnabler : public A {
    MakeSharedEnabler() : A() {
    }
};

shared_ptr A::Create() {
    return make_shared();
}

<답변5>

이것은 어떤가요?

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

<답변6>

struct A {
public:
  template std::shared_ptr static create(Arg&&...arg) {
    struct EnableMakeShared : public A {
      EnableMakeShared(Arg&&...arg) :A(std::forward(arg)...) {}
    };
    return std::make_shared(std::forward(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_;
};

<답변7>

이미 제공된 답변이 마음에 들지 않았기 때문에 이전 답변만큼 일반적이지 않은 솔루션을 검색하기로 결정했지만 더 좋습니다 (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 

template
struct MyAlloc : std::allocator
{
    void construct(void* p) { ::new(p) Private(); }
};

class A {
    public:
        static ::std::shared_ptr create() {
            return ::std::allocate_shared(MyAlloc());
        }

    protected:
        A() {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;

        friend struct MyAlloc;
};

int main() {
    auto p = A::create();
    return 0;
}

내가 작업중인 유틸리티를 기반으로하는 더 복잡한 예제입니다. 이것으로 Luc의 솔루션을 사용할 수 없었습니다. 그러나 Omnifarius의 제품은 적용 할 수 있습니다. 이전 예제에서는 누구나 MyAlloc을 사용하여 A 객체를 만들 수 있지만 create () 메서드 외에 A 또는 B를 만들 수있는 방법이 없습니다.

#include 

template
class safe_enable_shared_from_this : public std::enable_shared_from_this
{
    public:
    template
        static ::std::shared_ptr create(_Args&&... p_args) {
            return ::std::allocate_shared(Alloc(), std::forward<_Args>(p_args)...);
        }

    protected:
    struct Alloc : std::allocator
    {  
        template
        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 {
    private:
        A() {}
        friend struct safe_enable_shared_from_this::Alloc;
};

class B : public safe_enable_shared_from_this {
    private:
        B(int v) {}
        friend struct safe_enable_shared_from_this::Alloc;
};

int main() {
    auto a = A::create();
    auto b = B::create(5);
    return 0;
}

<답변8>

이상적으로는 완벽한 솔루션이 C ++ 표준에 추가되어야한다고 생각합니다. Andrew Schepler는 다음을 제안합니다.

(전체 스레드를 보려면 여기로 이동)

boost :: iterator_core_access에서 아이디어를 빌릴 수 있습니다. public 또는 protected 멤버가없는 새 클래스 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 
        static _T* __construct(void* __pv, _Args&& ... __args)
        { return ::new(__pv) _T(forward<_Args>(__args)...); }

        template 
        static void __destroy(_T* __ptr) { __ptr->~_T(); }

        template 
        friend class __shared_ptr_storage;
    };
}

위의 내용이 표준에 추가되면 다음을 수행합니다.

class A {
public:
   static std::shared_ptr create() {
      return std::make_shared();
   }

 protected:
   friend class std::shared_ptr_access;
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

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


<답변9>

이 스레드가 다소 오래되었다는 것을 알고 있지만 다른 곳에서는 볼 수 없었던 생성자에 대한 상속이나 추가 인수가 필요하지 않은 답변을 찾았습니다. 그러나 휴대용이 아닙니다.

#include 

#if defined(__cplusplus) && __cplusplus >= 201103L
#define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator::construct(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 create() { return std::make_shared(); }

};
int main() {
    std::shared_ptr t(test::create());
}

Windows와 Linux에서 테스트했으며 다른 플랫폼에 대해 조정이 필요할 수 있습니다.


<답변10>

두 개의 엄격하게 관련된 클래스 A와 B가 함께 작동 할 때 발생하는 더 복잡하고 흥미로운 문제가 있습니다.

A는 "마스터 클래스"이고 B는 "슬레이브"입니다. B의 인스턴스화를 A로만 제한하려면 B의 생성자를 비공개로 만들고 친구 B를 A에게 이렇게 설정합니다.

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

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

불행히도 A의 메서드에서 std :: make_shared ()를 호출 하면 컴파일러가 B :: B ()가 private이라는 것에 대해 불평하게됩니다.

이에 대한 내 해결책은 개인 생성자가 있고 A와 친구 인 B 내부에 공용 Pass 더미 클래스 (nullptr_t와 같음)를 만들고 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::Pass(), 42);
  }
};

class C
{
public:
  C()
  {
    // This is not
    auto ptr = std::make_shared(B::Pass(), 42);
  }
};

<답변11>

인수를받는 생성자를 활성화하려면이 방법이 도움이 될 수 있습니다.

#include 
#include 

template
struct enable_make : public S
{
    template
    enable_make(T&&... t)
        : S(std::forward(t)...)
    {
    }
};

class foo
{
public:
    static std::unique_ptr create(std::unique_ptr u, char const* s)
    {
        return std::make_unique>(std::move(u), s);
    }
protected:
    foo(std::unique_ptr u, char const* s)
    {
    }
};

void test()
{
    auto fp = foo::create(std::make_unique(3), "asdf");
}

<답변12>

[편집] 표준화 된 std :: shared_ptr_access <> 제안에 대해 위에서 언급 한 스레드를 읽었습니다. 그 안에 std :: allocate_shared <>에 대한 수정 사항과 그 사용 예에 대한 응답이있었습니다. 아래의 공장 템플릿에 맞게 수정하고 gcc C ++ 11 / 14 / 17에서 테스트했습니다. std :: enable_shared_from_this <>에서도 작동 하므로이 답변의 원래 솔루션보다 분명히 선호됩니다. 여기있어...

#include 
#include 

class Factory final {
public:
    template
    static std::shared_ptr make_shared(A&&... args) {
        return std::allocate_shared(Alloc(), std::forward(args)...);
    }
private:
    template
    struct Alloc : std::allocator {
        template
        void construct(U* ptr, A&&... args) {
            new(ptr) U(std::forward(args)...);
        }
        template
        void destroy(U* ptr) {
            ptr->~U();
        }
    };  
};

class X final : public std::enable_shared_from_this {
    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(42);
    auto p2 = p1->shared_from_this();
    std::cout << "p1=" << p1 << "\n"
              << "p2=" << p2 << "\n"
              << "count=" << p1.use_count() << "\n";
}

[원본] 공유 포인터 앨리어싱 생성자를 사용하는 솔루션을 찾았습니다. ctor와 dtor 모두 private이 될 수있을뿐만 아니라 최종 지정자를 사용할 수 있습니다.

#include 
#include 

class Factory final {
public:
    template
    static std::shared_ptr make_shared(A&&... args) {
        auto ptr = std::make_shared>(std::forward(args)...);
        return std::shared_ptr(ptr, &ptr->type);
    }
private:
    template
    struct Type final {
        template
        Type(A&&... args) : type(std::forward(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
};

class X final {
    friend struct Factory::Type;  // 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();
    auto ptr2 = Factory::make_shared(42);
}

초기 std :: shared_ptr <>은 유형 자체가 아니라 래퍼에 대한 것이기 때문에 위의 접근 방식은 std :: enable_shared_from_this <>에서 잘 작동하지 않습니다. 우리는 공장과 호환되는 동등한 클래스로이 문제를 해결할 수 있습니다.

#include 
#include 

template
class EnableShared {
    friend class Factory;  // factory access
public:
    std::shared_ptr shared_from_this() { return weak.lock(); }
protected:
    EnableShared() = default;
    virtual ~EnableShared() = default;
    EnableShared& operator=(const EnableShared&) { return *this; }  // no slicing
private:
    std::weak_ptr weak;
};

class Factory final {
public:
    template
    static std::shared_ptr make_shared(A&&... args) {
        auto ptr = std::make_shared>(std::forward(args)...);
        auto alt = std::shared_ptr(ptr, &ptr->type);
        assign(std::is_base_of, T>(), alt);
        return alt;
    }
private:
    template
    struct Type final {
        template
        Type(A&&... args) : type(std::forward(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
    template
    static void assign(std::true_type, const std::shared_ptr& ptr) {
        ptr->weak = ptr;
    }
    template
    static void assign(std::false_type, const std::shared_ptr&) {}
};

class X final : public EnableShared {
    friend struct Factory::Type;  // 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();
    auto ptr2 = ptr1->shared_from_this();
    std::cout << "ptr1=" << ptr1.get() << "\nptr2=" << ptr2.get() << "\n";
}

마지막으로 누군가는 clang이 Factory :: Type이 친구로 사용될 때 비공개라는 불만을 제기했다고 말했습니다. 그렇다면 그럴 경우 공개로 만드세요. 노출해도 해가되지 않습니다.


<답변13>

나는 같은 문제가 있었지만 보호 된 생성자에 인수를 전달해야하기 때문에 기존 답변 중 어느 것도 실제로 만족스럽지 않았습니다. 또한, 여러 클래스에 대해이 작업을 수행해야하며 각 클래스는 서로 다른 인수를 사용합니다.

그 효과를 위해 모두 유사한 방법을 사용하는 기존 답변 중 일부를 기반으로 다음과 같은 작은 덩어리를 제시합니다.

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

<답변14>

문제의 근본은 친구가 만든 함수 나 클래스가 생성자에 대해 하위 수준 호출을하는 경우에도 친구가되어야한다는 것입니다. std :: make_shared는 실제로 생성자를 호출하는 함수가 아니므로 친구를 사용하더라도 아무런 차이가 없습니다.

class A;
typedef std::shared_ptr APtr;
class A
{
    template
    friend class std::_Ref_count_obj;
public:
    APtr create()
    {
        return std::make_shared();
    }
private:
    A()
    {}
};

std :: _ Ref_count_obj는 실제로 생성자를 호출하므로 친구 여야합니다. 약간 모호하기 때문에 매크로를 사용합니다.

#define SHARED_PTR_DECL(T) \
class T; \
typedef std::shared_ptr ##T##Ptr;

#define FRIEND_STD_MAKE_SHARED \
template \
friend class std::_Ref_count_obj;

그러면 클래스 선언이 상당히 간단 해 보입니다. 원하는 경우 ptr 및 클래스를 선언하기위한 단일 매크로를 만들 수 있습니다.

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

이것은 실제로 중요한 문제입니다. 유지 관리가 가능하고 이식 가능한 코드를 만들려면 가능한 한 많은 구현을 숨겨야합니다.

typedef std::shared_ptr APtr;

스마트 포인터를 처리하는 방법을 숨기려면 typedef를 사용해야합니다. 그러나 항상 make_shared를 사용하여 생성해야하는 경우 목적이 무효화됩니다.

위의 예는 클래스를 사용하는 코드가 스마트 포인터 생성자를 사용하도록 강제합니다. 즉, 새로운 스마트 포인터로 전환하면 클래스 선언이 변경되고 완료 될 가능성이 높습니다. 다음 상사 나 프로젝트가 stl, boost 등의 계획을 언젠가 변경할 것이라고 가정하지 마십시오.

거의 30 년 동안이 작업을 수행하면서 몇 년 전에 잘못되었을 때이를 복구하기 위해 시간, 통증 및 부작용에 큰 대가를 지불했습니다.


<답변15>

이것을 사용할 수 있습니다 :

class CVal
{
    friend std::shared_ptr;
    friend std::_Ref_count;
public:
    static shared_ptr create()
    {
        shared_ptr ret_sCVal(new CVal());
        return ret_sCVal;
    }

protected:
    CVal() {};
    ~CVal() {};
};

<답변16>

#include 
#include 

class A : public std::enable_shared_from_this
{
private:
    A(){}
    explicit A(int a):m_a(a){}
public:
    template 
    static std::shared_ptr create(Args &&... args)
    {
        class make_shared_enabler : public A
        {
        public:
            make_shared_enabler(Args &&... args):A(std::forward(args)...){}
        };
        return std::make_shared(std::forward(args)...);
    }

    int val() const
    {
        return m_a;
    }
private:
    int m_a=0;
};

int main(int, char **)
{
    std::shared_ptr a0=A::create();
    std::shared_ptr a1=A::create(10);
    std::cout << a0->val() << " " << a1->val() << std::endl;
    return 0;
}
반응형