參考資料:
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
使用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
has_value(): 判斷容器中是否有值
reset(): 刪除容器中的值
std::any_cast
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