C++17新特性
- C++17新特性
- 前言
- If Statements with Initializer
- Constexpr if
- inline 變量
- 嵌套命名空間
- 屬性說明符
- string_view
- filesystem
- any
- optional
- variant
- execution
- 參考鏈接
前言
前項目
C++使用的時C++14標准,考慮到與時俱進,C++20也基本上確定,不過由於目前使用的linux(uos)上的自帶gcc並不能全面支持C++20, 所以暫時考慮新項目上C++17新標准,並且寫一篇完整的C++17中項目可能用到的新特性簡單文檔
visual studio 2019需要添加編譯參數 /std:c++17
If Statements with Initializer
帶初始化器的if語句
example
std::map<std::string, int> map;
map["nihao"] = 1;
map["shijie"] = 2;
if (auto ret = map.begin(); ret != map.end()) {
std::cout << ret->first << ": " << ret->second;
}
Constexpr if
在 constexpr if 語句中,條件 的值必須是可按語境轉換到 bool 類型的經轉換常量表達式。若其值為 true,則舍棄 false分支語句(若存在),否則舍棄 true分支語句。
通常我們寫業務很難用到,在模板元編程中會特別有用
example
template <typename T>
void TestConstexprIf(T value) {
if constexpr (std::is_integral_v<T>)
std::cout << "is integral" << std::endl;
else
static_assert(false, "T必須是整型");
}
inline 變量
inline 變量用來解決一個問題,全局變量再頭文件中定義之后多出使用產生符號重定義錯誤
錯誤例子
// test.h 頭文件
int test = 10;
// test1.cpp
void Function1() {
test = 20;
}
// test2.cpp
void Function() {
test = 30;
}
// 上面的代碼編譯將會產生重定義錯誤,c++17之前解決方案是使用extern導出全局變量
// 解決方案
// test.h 頭文件
extern int test;
// test.cpp
int test = 10;
C++17 之后引入inline變量使其全局變量可以直接再頭文件中聲明定義
example
inline int test = 10;
嵌套命名空間
example
namespace test::test2 {
int i = 0;
}
std::cout << test::test2::i << std::endl;
屬性說明符
屬性為各種由實現定義的語言擴展(例如 GNU 與 IBM 的語言擴展 __attribute__((...)),微軟的語言擴展 __declspec() 等)提供了統一化的語法。
[[fallthrough]] 標准屬性
指示從前一標號直落是有意的,而在發生直落時給出警告的編譯器不應診斷它。
C++17 之前的標准下, 有如下代碼
switch (device.status())
{
case sleep:
device.wake();
// fall thru
case ready:
device.run();
break;
case bad:
handle_error();
break;
}
C++17 可以這樣寫
switch (device.status())
{
case sleep:
device.wake();
[[fallthrough]];
case ready:
device.run();
break;
case bad:
handle_error();
break;
}
再之前的代碼編譯器會告訴你沒有break的警告,但是再c++17中使用fallthrough屬性就可以消除這個警告了
[[maybe_unused]] 標准屬性
可用來消除未使用的函數和變量編譯器所發出的警告
example
[[maybe_unused]] bool testUnusedVariable = true;
[[maybe_unused]]
void TestUnusedFunction() {
}
[[nodiscard]] 標准屬性
如果你的某個函數的返回值特別重要,希望使用者不要忽略,可以添加這個屬性,再編譯的時候如果函數使用者沒有使用返回值將會有一個警告產生
example
[[nodiscard]] bool TestNodiscard() {
return true;
}
[[deprecated]] 標准屬性
提示允許使用聲明有此屬性的名稱或實體,但因為一些原因不鼓勵使用,一般用在即將廢棄的函數,但是還有老的用戶使用到了這個函數
example
// 再vs中這個會不是警告而是錯誤.
[[deprecated("test deprecated")]] bool TestDeprecated() {
return true;
}
[[noretrun]] 標准屬性
告知函數並沒有返回值
example
[[noreturn]] void TestNoreturn() {
}
string_view
C++17 中特別新增的一個特別好用且重要的特性,string_view相對於string來說就是一個只讀的string,string_view的賦值操作的空間成本和時間成本遠遠勝於string,string_view的賦值特別像一個指針的賦值,一般來說再一下情況下使用string_view會更合適
exampel
// 常量string
const std::string = "hello world";
// string_view 更為合適
const string_view = "hello world";
// 函數參數
void Function1(const std::string& arg1) {
}
// string_view 更為合適
void Function1(string_view arg1) {
}
filesystem
在沒有C++17時一直就使用experiment/filesystem,在C++17 filesystem被正式納入C++標准庫中, 由於大多數人對filesystem都比較熟悉了,在這里只是簡單的介紹一下
example
std::filesystem::path path("testpath");
if (std::filesystem::exists(path)) {
// 存在
} else {
// 不存在
}
any
any是一個可用於任何類型單個值的類型安全容器,如果你之前有了解過boost相信對這個any類已經非常熟悉了
example
std::any Int = 69;
std::any Double = 69.123;
std::any String = std::string_view("Hello");
std::cout << Int.type().name() << std::endl;
std::cout << Double.type().name() << std::endl;
std::cout << Double.type().name() << std::endl;
std::vector<std::any> anys = { Int, Double, String };
std::cout << std::any_cast<int>(Int) << std::endl;
std::cout << std::any_cast<double>(Double) << std::endl;
std::cout << std::any_cast<std::string_view>(String) << std::endl;
// has_value: 是否有值
std::any a = 1;
if (a.has_value()) {
std::cout << a.type().name() << std::endl;// i
}
// reset:清空容器
a.reset();
if (a.has_value()) {
std::cout << "no value\n";// no value
}
optional
熟悉boost的也應該非常熟悉optional了,它最常用的地方是在你返回值是string或者int等出現錯誤之后非常隱式的表達的地方,使用std::optional就可以幫你解決這種問題
example
[[nodiscard]]
std::optional<int> TestOptional() {
// 之前我們可能需要使用return -1,代表錯誤,現在使用st::optional就不需要那種太過於隱式的表達
if (true) {
return 9999;
} else {
return std::nullopt;
}
}
[[nodiscard]]
std::optional<std::string> TestOptional2() {
// 之前我們可能需要使用return -1,代表錯誤,現在使用st::optional就不需要那種太過於隱式的表達
if (true) {
return "helloworld";
} else {
return std::nullopt;
}
}
// optional
auto result = TestOptional();
if (result.has_value()) {
// 有值,代表成功
} else {
// result沒有值代表失敗
}
// 這個value_or表示當TestOptional的返回值為nullopt時使用or里面的值
auto ret = TestOptional2().value_or("");
variant
variant用來表示一個類型安全的聯合體,variant 的一個實例在任意時刻要么保有其一個可選類型之一的值,要么在錯誤情況下無值。
variant 不容許保有引用、數組,或類型 void, 空variant可以使用std::variant<std::monostate>
下面這個例子是一個實際接口設計時利用std::variant解決不同類型不同參數的接口
// variant
struct SystemProxyConfig {
bool isService;
};
struct CustomProxyConfig {
bool isFile;
std::string pathOrContent;
};
std::variant<SystemProxyConfig, CustomProxyConfig> config;
//config = CustomProxyConfig{ false, "http://192.168.21.161/spiderweb.pac" };
config = SystemProxyConfig{ false };
if (std::get_if<CustomProxyConfig>(&config)) {
// 類型成功
CustomProxyConfig customConfig = std::get<CustomProxyConfig>(config);
} else {
// 類型失敗
SystemProxyConfig systemProxyConfig = std::get<SystemProxyConfig>(config);
int i = 0;
}
execution
execution為C++STL算法庫提供了一種算法的執行策略設置,目前支持的策略:
sequenced_policy(順序執行策略)parallel_policy(並行執行策略)parallel_unsequenced_policy(並行及無序執行策略)unsequenced_policy(無序執行策略)
example
std::vector<int> testExecution{ 1, 2, 3, 4, 5,8, 19, 20 ,30,40,50,0,102,40,10,30,20,1000,32,31,34,45};
auto it1 = std::find(std::execution::seq, testExecution.begin(), testExecution.end(), 5);
auto it2 = std::find(std::execution::par, testExecution.begin(), testExecution.end(), 5);
auto it3 = std::find(std::execution::par_unseq, testExecution.begin(), testExecution.end(), 5);
