c++ - 프로그램 - 유니크 포인터




스마트 포인터 란 무엇이며 언제 사용해야합니까? (10)

스마트 포인터 란 무엇이며 언제 사용해야합니까?


Chris, Sergdev 및 Llyod가 제공 한 정의가 정확합니다. 나는 더 단순한 정의를 선호한다. 내 인생을 단순하게 유지하는 것이다. 똑똑한 포인터는 간단히 ->* 연산자를 오버로드하는 클래스이다. 즉, 객체가 의미 론적으로 포인터처럼 보이지만 참조 카운팅, 자동 제거 등을 포함하여 더 시원한 것들을 할 수 있다는 것을 의미합니다. shared_ptrauto_ptr 은 대부분의 경우 충분하지만 작은 고유성 집합과 함께 제공됩니다.


기존 답변은 좋지만 스마트 포인터가 해결하려는 문제에 대한 (완전한) 해답이 아닐 때 수행 할 작업을 다루지는 않습니다.

스마트 포인터를 사용하는 다른 것들 (다른 답변에서 잘 설명 됨) 중에서도 우리는 추상 클래스를 함수 반환 유형으로 사용하는 방법에 대한 가능한 해결책이 무엇입니까? 이 질문의 사본으로 표시되었습니다. 그러나 C + +에서 반환 유형으로 추상적 인 (또는 사실상 모든) 기본 클래스를 지정하려는 유혹에 대한 첫 번째 질문은 "당신은 실제로 무엇을 의미합니까?"입니다. 부스트 포인터 컨테이너 라이브러리 의 문서에서 C ++의 관용적 객체 지향 프로그래밍 (다른 언어와 어떻게 다른지)에 대한 좋은 토론이 있습니다 . 요약하면 C ++에서는 소유권에 대해 생각해야합니다. 어느 스마트 포인터가 도움이되는지는 알 수 있지만, 유일한 해결책은 아니며, 항상 완벽한 솔루션입니다 (그들은 다형 사본을 제공하지 않습니다). 그리고 인터페이스에서 노출하고자하는 솔루션이 항상 아닙니다. (함수 반환은 무서운 것처럼 들립니다. 인터페이스와 같은 많은 것들). 예를 들어 참조를 반환하는 것으로 충분할 수 있습니다. 그러나 이러한 모든 경우 (스마트 포인터, 포인터 컨테이너 또는 단순히 참조를 반환하는 경우) 반환 을 어떤 형식의 참조로 변경했습니다 . 복사가 정말로 필요한 경우 더 많은 상용구 "관용구"를 추가하거나 C ++의 관용적 인 (또는 기타) OOP 이상으로 Adobe Poly 또는 Boost.TypeErasure 와 같은 라이브러리를 사용하는보다 일반적인 다형성으로 이동할 수 있습니다.


똑똑한 포인터는 포인터 자체가 범위를 벗어나고 포인터가 가리키는 포인터가 지워지는 것을 제외하고는 "char *"와 같은 일반 (유형이 지정된) 포인터와 같습니다. "->"를 사용하여 일반적인 포인터처럼 사용할 수 있지만 데이터에 대한 실제 포인터가 필요한 경우에는 사용할 수 없습니다. 이를 위해 "& * ptr"을 사용할 수 있습니다.

다음과 같은 경우에 유용합니다.

  • new로 할당해야하지만 같은 스택에 무언가와 동일한 수명을 유지하고자하는 객체. 스마트 포인터에 할당 된 개체는 프로그램이 해당 기능 / 블록을 종료 할 때 삭제됩니다.

  • 클래스의 데이터 멤버. 따라서 객체가 삭제 될 때 소멸자에 특별한 코드가 없어도 소유 된 모든 데이터가 삭제됩니다 (소멸자가 가상 ​​일 필요가 있습니다. 이는 항상 좋은 일입니다) .

다음과 같은 경우 스마트 포인터를 사용 하지 않을 수 있습니다.

  • ... 포인터는 실제로 데이터를 소유해서는 안됩니다 ... 즉, 데이터를 사용하고있을 때 포인터를 참조하고있는 함수에서 생존하기를 원할 때입니다.
  • ... 스마트 포인터 자체는 어느 시점에서 파괴되지 않습니다. 동적으로 할당되었지만 명시 적으로 삭제되지 않는 객체에서와 같이 결코 파괴되지 않는 메모리에 앉아 있기를 원하지 않습니다.
  • ... 두 개의 스마트 포인터가 동일한 데이터를 가리킬 수 있습니다. (그러나, 그것을 처리 할 더 똑똑한 포인터가 있습니다 ... 참조 카운팅 이라고 합니다 .)

참조 :


스마트 포인터는 가리키는 대상의 수명을 관리하기 위해 '원시'(또는 '베어') C ++ 포인터를 래핑하는 클래스입니다. 단일 스마트 포인터 유형은 없지만 모두 실용적인 방법으로 원시 포인터를 추상화하려고합니다.

스마트 포인터는 원시 포인터보다 선호되어야합니다. 포인터를 사용해야 할 필요가 있다고 느껴지면 (일반적으로 먼저 고려하십시오) 스마트 포인터를 사용하는 것이 일반적입니다. 원시 포인터의 문제를 상당 부분 해결할 수 있습니다. 주로 개체 삭제 및 메모리 누수를 잊어 버릴 수 있습니다.

원시 포인터를 사용하면 프로그래머는 객체가 더 이상 유용하지 않을 때 객체를 명시 적으로 파기해야합니다.

// Need to create the object to achieve some goal
MyObject* ptr = new MyObject(); 
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?

비교에 의한 스마트 포인터는 객체가 언제 파괴되는지에 대한 정책을 정의합니다. 여전히 개체를 만들어야하지만 더 이상 개체를 파괴 할 염려는 없습니다.

SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.

// Destruction of the object happens, depending 
// on the policy the smart pointer class uses.

// Destruction would happen even if DoSomething() 
// raises an exception

가장 간단한 정책은 boost::scoped_ptr 또는 std::unique_ptr 의해 구현되는 스마트 포인터 래퍼 객체의 범위를 포함합니다.

void f()
{
    {
       boost::scoped_ptr<MyObject> ptr(new MyObject());
       ptr->DoSomethingUseful();
    } // boost::scopted_ptr goes out of scope -- 
      // the MyObject is automatically destroyed.

    // ptr->Oops(); // Compile error: "ptr" not defined
                    // since it is no longer in scope.
}

scoped_ptr 인스턴스는 복사 할 수 없습니다. 이렇게하면 포인터가 여러 번 삭제되는 것을 방지 할 수 있습니다 (잘못). 그러나이 함수에 대한 참조를 다른 함수로 전달할 수는 있습니다.

범위 포인터는 개체의 수명을 특정 코드 블록에 연결하거나 개체 데이터로 다른 개체 내부에 포함 시키면 다른 개체의 수명이 길 때 유용합니다. 객체는 포함 된 코드 블록이 종료되거나 포함 객체가 파괴 될 때까지 존재합니다.

보다 복잡한 스마트 포인터 정책은 포인터를 계산하는 참조를 포함합니다. 이렇게하면 포인터를 복사 할 수 있습니다. 개체에 대한 마지막 "참조"가 삭제되면 개체가 삭제됩니다. 이 정책은 boost::shared_ptrstd::shared_ptr 의해 구현됩니다.

void f()
{
    typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
    MyObjectPtr p1; // Empty

    {
        MyObjectPtr p2(new MyObject());
        // There is now one "reference" to the created object
        p1 = p2; // Copy the pointer.
        // There are now two references to the object.
    } // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero. 
  // The object is deleted.

참조 계산 된 포인터는 개체의 수명이 훨씬 더 복잡하고 코드의 특정 섹션이나 다른 개체에 직접 연결되어 있지 않은 경우 매우 유용합니다.

카운트 된 포인터를 참조하는 데는 단점이 있습니다. 매달린 참조를 만들 가능성이 있습니다.

// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!

또 다른 가능성은 순환 참조를 만드는 것입니다.

struct Owner {
   boost::shared_ptr<Owner> other;
};

boost::shared_ptr<Owner> p1 (new Owner());
boost::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1

// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!

이 문제를 해결하기 위해 Boost와 C ++ 11은 weak_ptr 을 정의하여 shared_ptr 대한 weak (uncounted) 참조를 정의했습니다.

최신 정보

이 대답은 다소 오래되었고, Boost 라이브러리가 제공 한 현명한 포인터였던 시점에서 '좋은'것을 설명합니다. C ++ 11 이후 표준 라이브러리는 충분한 스마트 포인터 유형을 제공하므로 std::unique_ptr , std::shared_ptrstd::weak_ptr 의 사용을 선호해야합니다.

std::auto_ptr 있습니다. 그것은 스코프 된 포인터와 매우 흡사합니다. 단, 복사 할 수있는 "특별한"위험한 기능이 있습니다. 또한 예기치 않게 소유권을 이전합니다! 최신 표준에서는 사용되지 않으므로 사용하지 않아야합니다. 대신 std::unique_ptr 사용하십시오.

std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership. 
                                 // p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.

스마트 포인터는 일반 포인터의 래퍼 (wrapper) 인 클래스입니다. 일반 포인터와 달리 스마트 포인트 생명선은 참조 횟수 (스마트 포인터 개체가 할당 된 시간)를 기반으로합니다. 따라서 스마트 포인터가 다른 포인터에 할당 될 때마다 내부 참조 카운트에 더하기가 더해집니다. 그리고 물체가 범위를 벗어날 때마다 참조 횟수에서 마이너스를 뺀다.

자동 포인터는 비슷하지만 스마트 포인터와 완전히 다릅니다. 자동 포인터 객체가 변수 범위를 벗어날 때마다 리소스를 할당 해제하는 편리한 클래스입니다. 어느 정도까지는 포인터 (동적으로 할당 된 메모리에 대한 포인터)가 스택 변수 (컴파일 타임에 정적으로 할당 됨)와 유사하게 작동합니다.


스마트 포인터는 포인터와 같은 역할을하지만 추가적으로 구성, 파괴, 복사, 이동 및 참조 취소에 대한 제어 기능을 제공합니다.

하나는 자신의 스마트 포인터를 구현할 수 있지만 많은 라이브러리는 각각 다른 장점과 단점을 가진 스마트 포인터 구현을 제공합니다.

예를 들어 Boost 는 다음 스마트 포인터 구현을 제공합니다.

  • shared_ptr<T> 는 객체가 더 이상 필요하지 않을 때 참조 카운트를 사용하여 T 를 가리키는 포인터입니다.
  • scoped_ptr<T> 는 범위를 벗어날 때 자동으로 삭제 된 포인터입니다. 할당은 불가능합니다.
  • intrusive_ptr<T> 는 또 다른 참조 카운팅 포인터입니다. shared_ptr 보다 나은 성능을 제공하지만 자체 참조 계산 메커니즘을 제공하려면 T 유형이 필요합니다.
  • weak_ptr<T> 는 순환 참조를 피하기 위해 shared_ptr 과 함께 작동하는 약한 포인터입니다.
  • shared_array<T>shared_ptr 과 같지만 T 배열을 의미합니다.
  • scoped_array<T>scoped_ptr 과 같지만 T 배열입니다.

이것은 각각의 선형 설명 중 하나이며 필요에 따라 사용할 수 있습니다. 자세한 내용과 예제는 Boost 문서를 참조하십시오.

또한 C ++ 표준 라이브러리는 세 가지 스마트 포인터를 제공합니다. 고유 소유권에 대해서는 std::unique_ptr , 공유 소유권에 대해서는 std::shared_ptr , 공유 소유권에 대해서는 std::weak_ptr . std::auto_ptr 은 C ++ 03에 있지만 더 이상 사용되지 않습니다.


유사한 답변에 대한 링크는 다음과 같습니다. http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

스마트 포인터는 정상적인 포인터처럼 작동하고 외모를 느끼지만 더 많은 기능을 제공하는 객체입니다. C ++에서 스마트 포인터는 포인터를 캡슐화하고 표준 포인터 연산자를 재정의하는 템플릿 클래스로 구현됩니다. 그들은 일반 포인터보다 많은 장점이 있습니다. 그들은 널 포인터 또는 힙 오브젝트에 대한 포인터로 초기화되도록 보장됩니다. 널 포인터를 통한 간접 참조가 검사됩니다. 삭제가 필요하지 않습니다. 객체에 대한 마지막 포인터가 사라지면 객체는 자동으로 해제됩니다. 이러한 스마트 포인터의 중요한 문제점 중 하나는 일반 포인터와 달리 상속을 존중하지 않는다는 것입니다. 스마트 포인터는 다형성 코드에 적합하지 않습니다. 다음은 스마트 포인터 구현의 예입니다.

예:

template <class X>
class smart_pointer
{
          public:
               smart_pointer();                          // makes a null pointer
               smart_pointer(const X& x)            // makes pointer to copy of x

               X& operator *( );
               const X& operator*( ) const;
               X* operator->() const;

               smart_pointer(const smart_pointer <X> &);
               const smart_pointer <X> & operator =(const smart_pointer<X>&);
               ~smart_pointer();
          private:
               //...
};

이 클래스는 X 유형의 객체에 대한 스마트 포인터를 구현합니다. 객체 자체는 힙에 있습니다. 사용 방법은 다음과 같습니다.

smart_pointer <employee> p= employee("Harris",1333);

다른 오버로드 된 연산자와 마찬가지로 p는 일반 포인터처럼 동작합니다.

cout<<*p;
p->raise_salary(0.5);

이 튜토리얼에서 T를 클래스라고하자. C ++의 포인터는 세 가지 유형으로 나눌 수있다.

1) 원시 포인터 :

T a;  
T * _ptr = &a; 

메모리에있는 위치에 메모리 주소를 보유합니다. 프로그램을 계속 추적하기가 어려워 지므로주의해서 사용하십시오.

const 데이터 또는 주소가있는 포인터 {역방향 읽기}

T a ; 
const T * ptr1 = &a ; 
T const * ptr1 = &a ;

const 인 데이터 유형 T의 포인터. 포인터를 사용하여 데이터 유형을 변경할 수 없다는 것을 의미합니다. ie *ptr1 = 19 ; 작동하지 않을 것이다. 그러나 포인터를 움직일 수 있습니다. ie ptr1++ , ptr1-- ; 등 작동합니다. 뒤 읽기 : const 인 T를 가리키는 포인터

  T * const ptr2 ;

데이터 타입 T에 대한 const 포인터. 포인터를 움직일 수는 없지만 포인터가 가리키는 값을 변경할 수 있다는 의미입니다. ie *ptr2 = 19 는 작동하지만 ptr2++ ; ptr2-- 는 작동합니다 ptr2++ ; ptr2-- ptr2++ ; ptr2-- 등은 작동하지 않습니다. 역방향 읽기 : const는 유형 T에 포인터

const T * const ptr3 ; 

const 데이터 형 T에 대한 const 포인터. 포인터를 움직일 수 없거나 데이터 유형 포인터를 포인터로 변경할 수 없다는 것을 의미합니다. 즉. ptr3-- ; ptr3++ ; *ptr3 = 19; 작동하지 않을 것이다

3) 스마트 포인터 : { #include <memory> }

공유 포인터 :

  T a ; 
     //shared_ptr<T> shptr(new T) ; not recommended but works 
     shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe

     std::cout << shptr.use_count() ; // 1 //  gives the number of " 
things " pointing to it. 
     T * temp = shptr.get(); // gives a pointer to object

     // shared_pointer used like a regular pointer to call member functions
      shptr->memFn();
     (*shptr).memFn(); 

    //
     shptr.reset() ; // frees the object pointed to be the ptr 
     shptr = nullptr ; // frees the object 
     shptr = make_shared<T>() ; // frees the original object and points to new object

참조 카운팅을 사용하여 포인터가 가리키는 객체를 가리키는 "물건"의 수를 추적합니다. 이 개수가 0이되면 객체는 자동으로 삭제됩니다. 즉, 객체를 가리키는 모든 share_ptr이 범위를 벗어날 때 객체가 삭제됩니다. 이렇게하면 new를 사용하여 할당 한 객체를 삭제해야하는 번거 로움을 없앨 수 있습니다.

Weak Pointer : 공유 포인터를 사용할 때 발생하는 순환 참조를 처리합니다. 두 개의 공유 포인터가 가리키는 두 객체가 있고 서로 공유 포인터를 가리키는 내부 공유 포인터가 있으면 순환 참조가 있고 객체는 공유 포인터가 범위를 벗어날 때 삭제됩니다. 이를 해결하려면 내부 구성원을 shared_ptr에서 weak_ptr로 변경하십시오. 참고 : weak 포인터가 가리키는 요소에 액세스하려면 lock ()을 사용하고 weak_ptr을 반환합니다.

T a ; 
shared_ptr<T> shr = make_shared<T>() ; 
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr 
wk.lock()->memFn() ; // use lock to get a shared_ptr 
//   ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access

참조 : std :: weak_ptr은 언제 유용합니까?

고유 포인터 : 독점적 인 소유권을 지닌 경량 스마트 포인터. 포인터가 개체 사이를 공유하지 않고 포인터가 고유 한 개체를 가리킬 때 사용합니다.

unique_ptr<T> uptr(new T);
uptr->memFn(); 

//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr 

고유 한 ptr이 가리키는 객체를 변경하려면 move semantics를 사용하십시오.

unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1); 
// object pointed by uptr2 is deleted and 
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null 

References : 그것들은 본질적으로 const 포인터, 즉 const이고 더 나은 구문으로 움직일 수없는 포인터 일 수 있습니다.

참조 : C ++에서 포인터 변수와 참조 변수의 차이점은 무엇입니까?

r-value reference : reference to a temporary object   
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified 

참조 : https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ 이 질문을 지적한 Andre에게 감사드립니다.


http://en.wikipedia.org/wiki/Smart_pointer

컴퓨터 과학에서 스마트 포인터는 자동 가비지 수집 또는 경계 검사와 같은 추가 기능을 제공하면서 포인터를 시뮬레이트하는 추상 데이터 유형입니다. 이러한 추가 기능은 효율성을 유지하면서 포인터의 오용으로 인한 버그를 줄이기위한 것입니다. 스마트 포인터는 일반적으로 메모리 관리를 위해 포인터를 가리키는 객체를 추적합니다. 포인터의 오용은 버그의 주요 원인입니다. 포인터를 사용하여 작성된 프로그램에서 수행해야하는 상수 할당, 할당 해제 및 참조는 일부 메모리 누수가 발생할 가능성이 높습니다. 스마트 포인터는 리소스 할당 해제를 자동으로 만들어 메모리 누수를 방지하려고합니다. 예를 들어 포인터가 범위를 벗어나 뾰족한 객체가 파괴되기 때문에 객체에 대한 포인터 (또는 일련의 포인터에서 마지막 포인터)가 파괴되었습니다.


스마트 포인터 는 자동 메모리 할당 해제, 참조 계산 등과 같은 몇 가지 추가 기능이있는 포인터 모양의 유형입니다.

작은 소개는 페이지에 유효하다 똑똑한 포인터 - 무엇, 왜, 어느 것? .

간단한 스마트 포인터 유형 중 하나는 std::auto_ptr (C ++ 표준 20.4.5 장)입니다. 범위를 벗어나면 메모리를 자동으로 할당 해제 할 수 있으며 예외 발생시 간단한 포인터 사용보다 강력합니다. 융통성 있는.

또 다른 편리한 유형은 참조 계산을 구현하는 boost::shared_ptr 이며 객체에 대한 참조가 남아 있지 않을 때 메모리를 자동으로 할당 해제합니다. 이렇게하면 메모리 누수를 방지하고 RAII 를 구현하는 데 쉽게 사용할 수 있습니다.

제목은 David Vandevoorde, Nicolai M. Josuttis , Chapter 20. Smart Pointers에서 "C ++ Templates : The Complete Guide"라는 책에서 자세히 다룹니다. 일부 주제는 다음과 같습니다.

  • 예외로부터의 보호
  • 소지자 (참고 : std::auto_ptr 는 스마트 포인터의 이러한 유형의 구현입니다)
  • 자원 획득 초기화 (C ++의 예외적 인 자원 관리에 자주 사용됨)
  • 홀더 제한
  • 참조 횟수
  • 동시 카운터 액세스
  • 파괴와 할당 해제




c++-faq