簡體   English   中英

為什么unique_ptr :: operator *()沒有安全的替代方案?

[英]Why is there no safe alternative to unique_ptr::operator*()?

std::vector at()有成員函數作為operator[]的安全替代,因此應用綁定檢查並且不會創建懸空引用:

void foo(std::vector<int> const&x)
{
  const auto&a=x[0];     // What if x.empty()? Undefined behavior!
  const auto&a=x.at(0);  // Throws exception if x.empty().
}

但是, std::unique_ptr缺少相應的功能:

void foo(std::unique_ptr<int> const&x)
{
  const auto&a=*x;       // What if bool(x)==false? Undefined behavior!
}

如果std::unique_ptr有這樣一個安全的替代方案,那就太好了,比如成員ref() (和cref() ),它永遠不會返回一個懸空引用,而是拋出一個異常。 可能的實施:

template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
  if(bool(*this)==false)
    throw run_time_error("trying to de-refrence null unique_ptr");
  return this->operator*();
}

這個標准沒有提供這種東西有什么好的理由嗎?

unique_ptr專門設計為具有空狀態檢測的輕量級指針類(例如,在提議中以 可選方式聲明, 以添加實用程序類來表示可選對象(修訂版3)

也就是說,由於操作員*文檔聲明,您所要求的功能已經就位:

// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;

pointer類型定義為

std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*

因此,通過自定義刪除器,您可以執行任何即時操作,包括空指針檢查和異常拋出

#include <iostream>
#include <memory>

struct Foo { // object to manage
    Foo() { std::cout << "Foo ctor\n"; }
    Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
    Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
    ~Foo() { std::cout << "~Foo dtor\n"; }
};

struct Exception {};

struct InternalPtr {
    Foo *ptr = nullptr;
    InternalPtr(Foo *p) : ptr(p) {}
    InternalPtr() = default;

    Foo& operator*() const {
        std::cout << "Checking for a null pointer.." << std::endl;
        if(ptr == nullptr)
            throw Exception();
        return *ptr;
    }

    bool operator != (Foo *p) {
        if(p != ptr)
            return false;
        else
            return true;
    }
    void cleanup() {
      if(ptr != nullptr)
        delete ptr;
    }
};

struct D { // deleter
    using pointer = InternalPtr;
    D() {};
    D(const D&) { std::cout << "D copy ctor\n"; }
    D(D&) { std::cout << "D non-const copy ctor\n";}
    D(D&&) { std::cout << "D move ctor \n"; }
    void operator()(InternalPtr& p) const {
        std::cout << "D is deleting a Foo\n";
        p.cleanup();
    };
};

int main()
{
    std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved

    try {
      auto& e = *up;      
    } catch(Exception&) {
        std::cout << "null pointer exception detected" << std::endl;
    }

}

實例

為了完整起見,我將發布兩個額外的替代方案/解決方法:

  1. 指針通過operator bool檢查unique_ptr

     #include <iostream> #include <memory> int main() { std::unique_ptr<int> ptr(new int(42)); if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\\n'; ptr.reset(); if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\\n'; } 

    (這可能是解決問題的最重要方式)

  2. 另一種解決方案,雖然更麻煩,但是使用包裝類型來處理異常處理

我懷疑真正的答案很簡單,而且很多“為什么不是這樣的C ++?” 問題:

沒有人提出它。

std::vectorstd::unique_ptr不是由同一個人同時設計的,並且沒有以相同的方式使用,因此不一定遵循相同的設計原則。

我不能說,為什么委員會決定不添加安全的解除引用方法 - 答案可能是“因為它沒有提出”“因為原始指針也沒有” 但是自己編寫一個自由函數模板是很簡單的,它將任何指針作為參數,將它與nullptr進行比較,然后拋出異常或返回對指向對象的引用。

如果不通過指向基類的指針刪除它,甚至可以從unique_ptr公開派生,只需添加這樣的成員函數即可。

但請記住,在任何地方使用這種經過檢查的方法都可能會導致嚴重的性能損失(與at相同)。 通常,您希望最多驗證一次參數,開頭的單個if語句更適合。

還有學校說你不應該拋出異常來回應編程錯誤。 也許負責設計unique_ptr的peopke屬於這所學校,而設計矢量(更老的人)的人卻沒有。

智能指針API設計的主要目標之一是成為替代品,具有附加價值,沒有陷阱或副作用,並且接近零開銷。 if (ptr) ptr->...通常是如何安全地訪問裸指針,相同的語法與智能指針很好地協作,因此當一個被另一個替換時不需要更改代碼。

在指針內部進行額外的有效性檢查(例如,拋出異常)會干擾分支預測器,從而可能對性能產生連鎖效應,這可能不再被視為零成本直接替換。

你有

operator bool()

示例來自: cplusplusreference

// example of unique_ptr::operator bool
#include <iostream>
#include <memory>


int main () {
  std::unique_ptr<int> foo;
  std::unique_ptr<int> bar (new int(12));

  if (foo) std::cout << "foo points to " << *foo << '\n';
  else std::cout << "foo is empty\n";

  if (bar) std::cout << "bar points to " << *bar << '\n';
  else std::cout << "bar is empty\n";

  return 0;
}

unique_ptr是原始指針的簡單包裝器,當您只需輕松檢查布爾條件時就不需要拋出異常。

編輯:顯然操作員*可以拋出。

異常1)可能會拋出,例如,如果指針定義了拋出運算符*

也許有人可以在熱點上擺出一些燈來定義投擲操作員*

根據MikeMB的建議,這里有一個自由函數的可能實現,用於解除引用指針和unique_ptr等。

template<typename T>
inline T& dereference(T* ptr) noexcept(false)
{
  if(!ptr) throw std::runtime_error("attempt to dereference a nullptr");
  return *ptr;
}

template<typename T>
inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false)
{
  if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)");
  return *ptr;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM