스마트 포인터보다 원시 포인터를 언제 사용해야합니까?
이 답변을 읽은 후 가능한 한 스마트 포인터 를 사용하고 "일반"/ 원시 포인터 사용을 최소로 줄이는 것이 모범 사례 인 것 같습니다 .
사실인가요?
아니요, 사실이 아닙니다. 함수에 포인터가 필요하고 소유권과 관련이없는 경우 다음과 같은 이유로 일반 포인터가 전달되어야한다고 강력하게 믿습니다.
- 소유권이 없으므로 어떤 종류의 스마트 포인터를 전달할지 모릅니다.
- 같은 특정 포인터를 전달하면
shared_ptr
다음 과 같이 전달할 수 없습니다.scoped_ptr
규칙은 다음과 같습니다. 개체가 특정 종류의 개체 소유권을 가져야한다는 것을 알고 있다면 항상 스마트 포인터를 사용하세요.이 포인터는 필요한 소유권을 제공합니다. 소유권 개념이 없으면 스마트 포인터를 사용 하지 마십시오.
예 1 :
void PrintObject(shared_ptr<const Object> po) //bad
{
if(po)
po->Print();
else
log_error();
}
void PrintObject(const Object* po) //good
{
if(po)
po->Print();
else
log_error();
}
예 2 :
Object* createObject() //bad
{
return new Object;
}
some_smart_ptr<Object> createObject() //good
{
return some_smart_ptr<Object>(new Object);
}
스마트 포인터를 사용하여 소유권을 관리하는 것은 올바른 일입니다. 반대로 소유권이 문제가 되지 않는 곳에서 원시 포인터를 사용 하는 것은 잘못된 것이 아닙니다 .
다음은 완전히 합법적 인 원시 포인터 사용입니다 (항상 소유하지 않은 것으로 간주됨을 기억하십시오).
참조와 경쟁하는 곳
- 인수 전달; 그러나 참조는 null이 될 수 없으므로 바람직합니다.
- 구성보다는 연관성을 나타내는 반원으로서; 일반적으로 할당의 의미가 더 간단하고 생성자에 의해 설정된 불변성
0
이 객체의 수명 기간 동안 이 아닌지 확인할 수 있기 때문에 일반적으로 참조보다 선호됩니다. - 다른 곳에서 소유 한 (다형적일 가능성이있는) 객체에 대한 핸들로; 참조는 null이 될 수 없으므로 다시 선호됩니다.
std::bind
전달 된 인수가 결과 펑터에 복사되는 규칙을 사용합니다. 그러나std::bind(&T::some_member, this, ...)
포인터의 복사본 만 만들고std::bind(&T::some_member, *this, ...)
개체를 복사합니다.std::bind(&T::some_member, std::ref(*this), ...)
대안이다
참조와 경쟁 하지 않는 경우
- 반복자로!
- 선택적 매개 변수 의 인수 전달 ; 여기서 그들은 경쟁한다
boost::optional<T&>
- 초기화 사이트에서 선언 할 수없는 경우 다른 곳에서 소유 한 (다형적일 수 있음) 객체에 대한 핸들로; 다시, 경쟁
boost::optional<T&>
다시 말씀 드리지만, 그것은 (생성자에 전달 차례에하지 않는 한 스마트 포인터를 받아들 (생성자, 또는 예를 들어 소유권을 소요 함수 구성원이 아닌) 함수를 작성하기 위해 거의 항상 잘못 예를 들어, 그것은 맞습니다에 대한 std::async
의미가 있기 때문에 std::thread
생성자 에 대한 호출에 가깝습니다 ). 동기식이면 스마트 포인터가 필요하지 않습니다.
요약하자면 위의 몇 가지 용도를 보여주는 스 니펫이 있습니다. 우리는 std::vector<int>
출력을 쓰는 동안 모든 요소에 펑터를 적용하는 클래스를 작성하고 사용하고 있습니다.
class apply_and_log {
public:
// C++03 exception: it's acceptable to pass by pointer to const
// to avoid apply_and_log(std::cout, std::vector<int>())
// notice that our pointer would be left dangling after call to constructor
// this still adds a requirement on the caller that v != 0 or that we throw on 0
apply_and_log(std::ostream& os, std::vector<int> const* v)
: log(&os)
, data(v)
{}
// C++0x alternative
// also usable for C++03 with requirement on v
apply_and_log(std::ostream& os, std::vector<int> const& v)
: log(&os)
, data(&v)
{}
// now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
// && is also acceptable instead of const&&
apply_and_log(std::ostream& os, std::vector<int> const&&) = delete;
// Notice that without effort copy (also move), assignment and destruction
// are correct.
// Class invariants: member pointers are never 0.
// Requirements on construction: the passed stream and vector must outlive *this
typedef std::function<void(std::vector<int> const&)> callback_type;
// optional callback
// alternative: boost::optional<callback_type&>
void
do_work(callback_type* callback)
{
// for convenience
auto& v = *data;
// using raw pointers as iterators
int* begin = &v[0];
int* end = begin + v.size();
// ...
if(callback) {
callback(v);
}
}
private:
// association: we use a pointer
// notice that the type is polymorphic and non-copyable,
// so composition is not a reasonable option
std::ostream* log;
// association: we use a pointer to const
// contrived example for the constructors
std::vector<int> const* data;
};
스마트 포인터는 소유권을 명확하게 문서화하므로 항상 사용하는 것이 좋습니다.
What we really miss, however, is a "blank" smart pointer, one that does not imply any notion of ownership.
template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
ptr(T* p): _p(p) {}
template <typename U> ptr(U* p): _p(p) {}
template <typename SP> ptr(SP const& sp): _p(sp.get()) {}
T& operator*() const { assert(_p); return *_p; }
T* operator->() const { assert(_p); return _p; }
private:
T* _p;
}; // class ptr<T>
This is, indeed, the simplest version of any smart pointer that may exist: a type that documents that it does not own the resource it points too.
One instance where reference counting (used by shared_ptr in particular) will break down is when you create a cycle out of the pointers (e.g. A points to B, B points to A, or A->B->C->A, or etc). In that case, none of the objects will ever be automatically freed, because they are all keeping each other's reference counts greater than zero.
For that reason, whenever I am creating objects that have a parent-child relationship (e.g. a tree of objects), I will use shared_ptrs in the parent objects to hold their child objects, but if the child objects need a pointer back to their parent, I will use a plain C/C++ pointer for that.
Few cases, where you may want to use pointers:
- Function pointers (obviously no smart pointer)
- Defining your own smart pointer or container
- Dealing with low level programming, where raw pointers are crucial
- Decaying from raw arrays
I think a little bit more thorough answer was given here: Which kind of pointer do I use when?
Excerpted from that link: "Use dumb pointers (raw pointers) or references for non-owning references to resources and when you know that the resource will outlive the referencing object / scope." (bold preserved from the original)
The problem is that if you're writing code for general use it's not always easy to be absolutely certain the object will outlive the raw pointer. Consider this example:
struct employee_t {
employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
std::string m_first_name;
std::string m_last_name;
};
void replace_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
employee_list.clear();
employee_list.push_back(*p_new_employee);
}
void main(int argc, char* argv[]) {
std::list<employee_t> current_employee_list;
current_employee_list.push_back(employee_t("John", "Smith"));
current_employee_list.push_back(employee_t("Julie", "Jones"));
employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());
replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}
Much to its surprise, the replace_current_employees_with()
function can inadvertently cause one of its parameters to be deallocated before it's finished using it.
So even though it might at first seem like the replace_current_employees_with()
function doesn't need ownership of it's parameters, it needs some kind of defense against the possiblity of its parameters being insidiously deallocated before it's finished using them. The simplest solution is to actually take (temporary shared) ownership of the parameter(s), presumably through a shared_ptr
.
But if you really don't want to take ownership, there is now a safe option - and this is the shameless plug portion of the answer - "registered pointers". "registered pointers" are smart pointers that behave like raw pointers, except that they are (automatically) set to null_ptr
when the target object is destroyed, and by default, will throw an exception if you try to access an object that has already been deleted.
Also note that registered pointers can be "disabled" (automatically replaced with their raw pointer counterpart) with a compile-time directive, allowing them to be used (and incur overhead) in debug/test/beta modes only. So you should really have to resort actual raw pointers quite rarely.
It is true. I can not see the benefits of raw pointers over smart pointers, especially in a complex project.
For tempory and lightweight usage, raw pointers are fine though.
ReferenceURL : https://stackoverflow.com/questions/6675651/when-should-i-use-raw-pointers-over-smart-pointers
'IT박스' 카테고리의 다른 글
VBA (Excel)에서 오류를 올바르게 처리 (0) | 2020.12.30 |
---|---|
C / C ++ 파일에서 Android NDK의 컴파일을 감지하는 방법은 무엇입니까? (0) | 2020.12.30 |
MongoDB-컬렉션 내 중첩 된 항목을 쿼리하는 방법은 무엇입니까? (0) | 2020.12.30 |
HTML SELECT-옵션이 변경되지 않은 경우에도 JavaScript ONCHANGE 이벤트 트리거 (0) | 2020.12.30 |
명명 된 기본 제약 조건 및 명명 된 외래 키 제약 조건으로 테이블 추가 열을 변경하는 방법은 무엇입니까? (0) | 2020.12.30 |