C++筆記-std::any、void*和shared_ptr


參考資料:
C++17之std::any https://blog.csdn.net/janeqi1987/article/details/100568181
std::any: How, when, and why: https://devblogs.microsoft.com/cppblog/stdany-how-when-and-why/

1.簡介

1.1 為什么不用void* ?

struct day { 
  // ...things... 
  void* user_data; 
}; 

struct month { 
  std::vector<day> days; 
  void* user_data; 
};

a. void*不能保證類型安全,你可以將一個void * 賦給 Foo *,無論它指向的對象是否實際上是Foo類的

some_day.user_data = new std::string{"Hello, World!"}; 
// …much later 
Foo* some_foo = static_cast<Foo*>(some_day.user_data); 
some_foo->frobnicate(); // BOOM!

b. void *不能像智能指針那樣管理生命周期,因此客戶端必須手動管理關聯數據的生命周期。錯誤會導致內存泄漏

delete some_day.user_data; 
some_day.user_data = nullptr; 
some_month.days.clear(); // Oops: hopefully none of these days had 
                         // non-null user_data

c. 庫無法復制void *指向的對象,因為它不知道對象的類型

some_month.days[0] = some_month.days[1]; 
if (some_month.days[1].user_data) { 
  // user_data內保存了一個string,如果不希望在days之間共享同一個string,需要手動copy
  std::string const& src = *some_month.days[1].user_data; 
  some_month.days[0].user_data = new std::string(src); 
}

1.2 為什么不用shared_ptr<void>

使用shared_ptr<void>代替void*可以解決聲明周期管理的問題。shared_ptr有足夠的類型信息以了解如何正確銷毀它指向的對象。

struct day {
  // ...things...
  std::shared_ptr<void> user_data;
};
struct month {
  std::vector<day> days;
  std::shared_ptr<void> user_data;
};
......
some_day.user_data = std::make_shared<std::string>("Hello, world!");
// ...much later...
some_day = some_other_day; // the object at which some_day.user_data _was_
                           // pointing is freed automatically

但是std::shared_ptr 和void*一樣不能解決類型安全的問題。
使用shared_ptr 需要reinterpreting integral 為void *並直接存儲它們來避免內存分配;使用shared_ptr強制我們甚至為諸如int之類的微小對象分配內存。

1.3 C++17引入引入了std::any

定義在any頭文件中:#include <any>
是一個可用於任何類型單個值的類型安全的容器.
類型安全:每個對象在定義時被分配一個類型。對於一個程序或者程序的一部分,如果使用的對象符合它們規定的類型,那么它們是類型安全的。
std: any是一種值類型,它能夠更改其類型,同時仍然具有類型安全性。也就是說,對象可以保存任意類型的值,但是它們知道當前保存的值是哪種類型。在聲明此類型的對象時,不需要指定可能的類型
 訣竅在於,對象同時擁有包含的值使用typeid包含值的類型。因為這個值可以有任何大小,所以可以在堆上分配內存,鼓勵實現避免小對象的動態分配。也就是說,如果分配一個字符串,對象將為該值分配內存並復制該字符串,同時也在內部存儲分配的字符串。稍后,可以執行運行時檢查來確定當前值的類型,並使用any_cast<該值的類型>獲取值。

#include <typeinfo.h>
#include <any>

std::any a; // a is empty
std::any b = 4.3; // b has value 4.3 of type double

a = 42; // a has value 42 of type int
b = std::string{"hi"}; // b has value "hi" of type std::string

if (a.type() == typeid(std::string)) {
    std::string s = std::any_cast<std::string>(a);
    useString(s);
}
else if (a.type() == typeid(int)) {
    useInt(std::any_cast<int>(a));
}

小結
std::any a = 1;: 聲明一個any類型的容器,容器中的值為int類型的1
a.type(): 得到容器中的值的類型
std::any_cast (a);: 強制類型轉換, 轉換失敗可以捕獲到std::bad_any_cast類型的異常
has_value(): 判斷容器中是否有值
reset(): 刪除容器中的值
std::any_cast (&a): 強制轉換得到容器中的值的地址

2.std::any操作

函數 說明
constructors 創建一個any對象(可能調用底層類型的構造函數)
make_any() 創建一個any對象(傳遞值來初始化它)
destructor 銷毀any對象
= 分配一個新值
emplace () 分配一個類型為T的新值
reset() 銷毀any對象的值(使對象為空)
has_value() 返回對象是否具有值
type() 返回當前類型為std::type_info對象
any_cast () 使用當前值作為類型T的值(如果其他類型除外)
swap() 交換兩個any對象的值

2.1 構造函數

默認情況下,std::any的初始值為空。

std::any a1; // a1 is empty
如果傳遞一個值進行初始化,則將其衰減類型用作所包含值的類型:

std::any a2 = 42; // a2 contains value of type int
std::any a3 = "hello"; // a2 contains value of type const char*

要保存與初始值類型不同的類型,必須使用in_place_type標記:

std::any a4{std::in_place_type<long>, 42};
std::any a5{std::in_place_type<std::string>, "hello"};

make_any<>(),可以用於單個或多個參數(不需要in_place_type參數)。必須顯式指定初始化的類型(如果只傳遞一個參數,則不會推導出初始化的類型):
注:std::make_any會新建對象

auto a10 = std::make_any<float>(3.0);
auto a11 = std::make_any<std::string>("hello");
auto a13 = std::make_any<std::complex<double>>(3.0, 4.0);
auto a14 = std::make_any<std::set<int,decltype(sc)>>({4, 8, -7, -2, 0, 5}, sc);

2.2 訪問值

要訪問包含的值,必須使用std::any_cast<>將其轉換為其類型。將該值轉換為一個字符串,有幾個選項:

std::any_caststd::string(a) // yield copy of the value
std::any_caststd::string&(a); // write value by reference
std::any_cast<const std::string&>(a); // read-access by reference

如果把std::any中所包含的類型轉換為移除了傳遞類型的頂層引用后的類型ID,則轉換類型是適合的。如下:

const auto& s = std::make_any<std::string>("hello");    
if (s.type() == typeid(std::string)) { //刪除頂層cosnt和引用后的類型
    auto a = std::any_cast<std::string>(s);
    std::cout << a << std::endl;
}

2.3 修改值

相應的賦值和emplace()操作。例如:

std::any a;
a = 42; // a contains value of type int
a = "hello"; // a contains value of type const char* 
a.emplace<std::string>("hello world");// a contains value of type std::string

2.4 移動語法

std: any也支持移動語義。但是,請注意,move語義必須滿足包含的類型具有可復制構造函數。也就是說,不支持只移動類型作為包含值類型。處理move語義的最佳方法可能並不明顯。所以,你應該這樣做:

std::string s("hello, world!");
std::any a;
a = std::move(s); // move s into a
s = std::move(std::any_cast<string&>(a)); // move assign string in a to s
//這種可能有問題,只有當包含的值已經是字符串時,才可以這樣做。如果沒有,轉換將拋出一個std::bad_any_cast異常。
std::string s3 = std::any_cast<std::string&>(std::move(a));
std::cout << s3 << std::endl;

與通常的從對象移動的情況一樣,在最后一次調用之后,所包含的值a是未指定的。因此,可以使用a作為字符串,只要沒有對所包含的字符串值的值做任何假設。

注意

s = std::any_cast<string>(std::move(a));

也可以,但需要一個額外的移動。然而,以下內容是危險的(盡管它是c++標准中的一個例子):

std::any_cast<string&>(a) = std::move(s2); // OOPS: a to hold a string

只有當包含的值已經是字符串時,才可以這樣做。如果沒有,轉換將拋出一個std::bad_any_cast異常。

注意:
std::any_cast<>創建了一個傳遞類型的對象。如果將std::string作為模板參數傳遞給std::any_cast<>,它將創建一個臨時string(一個prvalue),然后用它初始化新對象s。如果沒有這樣的初始化,通常最好轉換為引用類型,以避免創建臨時對象:

std::cout << std::any_cast<const std::string&>(a);

要修改該值,需要轉換為對應的引用類型:

std::any_cast<std::string&>(a) = "world";

注意2:
可以為std::any對象的地址調用std::any_cast。在這種情況下,如果類型匹配,則強制轉換返回相應的地址指針;如果不匹配,則返回nullptr:

auto p = std::any_cast<std::string>(&a);
if (p) {
   ...
}

注意3:
是使用衰減類型存儲的(數組轉換為指針,忽略頂層引用和const)。對於字符串常量,這意味着值類型是const char*。要檢查type()並使用std::any_cast<>,必須使用以下類型:

std::any a = "hello"; // type() is const char*
if (a.type() == typeid(const char*)) { // true
  ...
}
if (a.type() == typeid(std::string)) { // false
  ...
}
std::cout << std::any_cast<const char*>(v[1]) << '\n'; // OK
std::cout << std::any_cast<std::string>(v[1]) << '\n'; // EXCEPTION


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM