2011-07-13 59 views
43

看完this answer之後,看起來最好儘量使用smart pointers,並將「正常」/原始指針的使用量降至最低。什麼時候應該在智能指針上使用原始指針?

這是真的嗎?

+15

只是爲了記錄,許多智能指針類型*與原始指針一樣快。只是當你說「智能指針」時,幾乎每個人都聽到它是「共享指針」,並且共享指針比原指針慢得多。但'scoped_ptr'或'unique_ptr'沒有性能開銷。所以「我想要表現」並不是避免智能指針的真正藉口。僅用於避免'shared_ptr'特定 – jalf

回答

62

不,這是不正確的。如果一個函數需要一個指針和無關所有權,那麼我強烈相信,一個普通指針應,原因如下傳遞:

  • 沒有所有權,所以你不知道什麼樣的智能的指針傳遞
  • 如果你傳遞一個特定的指針,就像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); 
} 
+11

Example1中的一個替代方法是傳遞一個引用,因爲沒有所有權被傳遞 - 就像在舊的'auto_ptr'中一樣。 – DanS

+12

我喜歡關於所有權的說明(這是智能指針的全部內容)。但是,您可能需要考慮PrintObject應採用const而不是const *。這樣,您就可以保證不能轉讓所有權,所以PrintObject不應該佔有它。 –

+0

@DanS:傳遞一個auto_ptr會簡直是錯誤的 - 它會在函數完成後破壞對象 –

1

少數情況下,您可能需要使用指針:

  • 函數指針(顯然沒有智能指針)
  • 定義您自己的智能指針或容器
  • 低層次的編程,其中原始指針是至關重要的
  • 衰變從原材料陣列
+0

雖然你可以使用容器而不是數組或智能數組,但是爲什麼低級編程需要原始指針但是你沒有指定它們)? –

+0

有一些類似於函數的智能指針:'boost :: function'或'std :: function'。它們是處理複製等的各種類型的包裝器功能對象,函數指針等。 –

2

一個實例就是當你出去的創建循環引用計數(在特定的shared_ptr使用)會分解爲應對指針(例如, A指向B,B指向A,或A-> B-> C-> A等)。在這種情況下,沒有任何對象會被自動釋放,因爲它們都保持彼此的引用計數大於零。因此,無論何時我創建具有父子關係的對象(例如對象樹),我都會在父對象中使用shared_ptrs來保存它們的子對象,但是如果子對象需要一個指針回到他們的父母,我將使用一個普通的C/C++指針。

+6

好點,但仍然是弱指針,像'boost :: weak_ptr' –

+0

啊,是的,我忘記了weak_ptr。 –

6

總是推薦使用智能指針,因爲它們清楚地記錄了所有權。

然而,我們真正想念的是一個「空白」智能指針,它並不意味着任何所有權概念。

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> 

這確實是,任何可能存在的智能指針的簡單版本:該文檔,它不擁有它指向太資源類型。

+7

我不明白這是一個「智能」指針。它是一個原始指針的包裝,它既不共享所有權,也不會檢測指向對象何時被刪除。它提供了一個簡單的原始指針有什麼優勢? – 2011-07-13 08:49:19

+5

@OrbWeaver:功能上?沒有。然而,從語義上講,它明確表示所有權是被考慮的,並且決定該變量不會擁有所有權。當你看到一個void foo(Bar *)方法時,這個方法是否擁有指針的所有權總是含糊不清的,如果你看到'void foo(client_ptr )',那麼你知道它沒有取得所有權 - 雖然我會對更好的名字感興趣:) –

+1

哦,我明白了。有趣的技術,我從來沒有想過以這種方式使用類。 – 2011-07-13 08:59:22

13

使用智能指針來管理所有權是正確的。 相反,使用原始指針無論所有權是而不是問題是不是錯誤。

這裏有一些完全合法的使用原始指針(請記住,它總是以爲他們都是非所屬)的:

在那裏他們與引用

  • 參數傳遞競爭;但引用不能爲空,所以優先使用
  • 作爲類成員來表示關聯而不是成分;通常比引用更可取,因爲賦值的語義更直接,另外由構造函數設置的不變量可以確保它們不是對象的生命週期的0作爲對某個(可能是多態的)對象擁有的句柄的對象的句柄
  • 其他;引用不能爲空,因此它們也是可取的
  • 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; 
}; 
1

我覺得有點更全面的答案在這裏給出:Which kind of pointer do I use when?

從該鏈接摘錄:「使用dumb指針(裸指針)或引用爲非所屬引用資源和時你知道資源將超過引用對象/範圍的。「(黑體從原來保存)

的問題是,如果你正在寫的一般使用代碼它並不總是很容易以絕對的對象會活得比原始指針考慮這個例子:

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

出乎其意外,之前它使用完被釋放的replace_current_employees_with()功能,可以在不經意間造成它的一個參數。

所以,即使它聽起來有點像replace_current_employees_with()功能並不需要擁有它的參數,它需要對可能的某種防禦在使用完參數之前,其參數會被陰險地釋放。最簡單的解決方案是實際採取(臨時共享)參數的所有權,大概通過shared_ptr

但是,如果你真的不想取得所有權,現在有一個安全的選擇 - 這是答案的無恥插件部分 - 「registered pointers」。 「已註冊的指針」是智能指針,它們的行爲與原始指針類似,只不過它們在目標對象被銷燬時自動設置爲null_ptr,並且默認情況下會在嘗試訪問已被刪除的對象時引發異常。

還要注意,已註冊的指針可以使用編譯時指令「禁用」(自動替換爲其原始指針對應部分),只允許它們在調試/測試/測試模式下使用(併產生開銷)。所以你應該非常少用實際的原始指針。

-1

確實如此。我看不到智能指針的原始指針的好處,特別是在複雜的項目中。

對於暫時性和輕量級的使用,原始指針雖然沒問題。