天天看點

c++20 新增的一些小的特性

作者:程式員小x

c++20新增的特性非常多,其中concept,coroutine,module和range為四大特性,在之前的章節中已有涉及,本文則對其他的一些小改動進行講解。

  • std::format
  • Calendar
  • timezone
  • std::source_location
  • std::span航天飛機運算符 <=>
  • std::endian判斷大小端
  • std::remove_cvref
  • bind_frontstd::atomic_ref
  • std::map<Key,T,Compare,Allocator>::contains
  • std::barrier
  • std::latch鎖存器
  • std::counting_semaphore
  • string::starts_with / ends_with
  • std::size
  • std::is_bounded_array_v和std::is_unbounded_array
  • std::erase_if
  • Mathematical constants
  • std::midpointstd::lerp

std::format

c++20支援新的字元串格式化方式std::format

#include <format>
#include <iostream>
  
using namespace std;
  
int main()
{
    // Declare variables
    int num = 42;
    std::string name = "John";
  
    // Use std::format to format a string with placeholders
    // for variables
    std::string formatted_str = std::format(
        "My name is {} and my favorite number is {}", name,
        num);
  
    // Print formatted string to console
    std::cout << formatted_str << std::endl;
  
    return 0;
}
           

Calendar

#include <chrono>

int main()
{
    using namespace std::chrono;

    // Get a local time_point with system_clock::duration precision
    auto now = zoned_time{current_zone(), system_clock::now()}.get_local_time();

    // Get a local time_point with days precision
    auto ld = floor<days>(now);

    // Convert local days-precision time_point to a local {y, m, d} calendar
    year_month_day ymd{ld};

    // Split time since local midnight into {h, m, s, subseconds}
    hh_mm_ss hms{now - ld};

    // This part not recommended.  Stay within the chrono type system.
    int year{ymd.year()};
    int month = unsigned{ymd.month()};
    int day = unsigned{ymd.day()};
    int hour = hms.hours().count();
    int minute = hms.minutes().count();
    int second = hms.seconds().count();
}
           

timezone

時區工具

#include <chrono>
#include <iostream>
 
int main() {
    const std::chrono::zoned_time cur_time{ std::chrono::current_zone(),
                                            std::chrono::system_clock::now() };
    std::cout << cur_time << '\n';
}
           

std::source_location

檔案位置的工具

#include <iostream>
#include <source_location>
#include <string_view>
 
void log(const std::string_view message,
         const std::source_location location =
               std::source_location::current())
{
    std::clog << "file: "
              << location.file_name() << '('
              << location.line() << ':'
              << location.column() << ") `"
              << location.function_name() << "`: "
              << message << '\n';
}
 
template<typename T>
void fun(T x)
{
    log(x);
}
 
int main(int, char*[])
{
    log("Hello world!");
    fun("Hello C++20!");
}
           

std::span

過去如果一個函數想接受無法确定數組長度的數組作為參數,那麼一定需要聲明兩個參數:數組指針和長度:

void set_data(int *arr, int len) {}

int main()
{
    int buf[128]{ 0 };
    set_data(buf, 128);
}
           

這種人工輸入增加了編碼的風險,數組長度的錯誤輸入會引發程式的未定義行為,甚至是成為可被利用的漏洞。C++20标準庫為我們提供了一個很好解決方案std::span,通過它可以定義一個基于連續序列對象的視圖,包括原生數組,并且保留連續序列對象的大小。例如:

#include <iostream>
#include <span>
void set_data(std::span<int> arr) {
    std::cout << arr.size();
}

int main()
{
    int buf[128]{ 0 };
    set_data(buf);
}
           

航天飛機運算符 <=>

(a <=> b) < 0 if a < b,
(a <=> b) > 0 if a > b,
(a <=> b) == 0 if a and b are equal/equivalent.
           
#include <compare>
#include <iostream>
 
int main()
{
    double foo = -0.0;
    double bar = 0.0;
 
    auto res = foo <=> bar;
 
    if (res < 0)
        std::cout << "-0 is less than 0";
    else if (res > 0)
        std::cout << "-0 is greater than 0";
    else if (res == 0)
        std::cout << "-0 and 0 are equal";
    else
        std::cout << "-0 and 0 are unordered";
}
           

std::endian判斷大小端

#include <bit>
#include <iostream>
 
int main()
{
    if constexpr (std::endian::native == std::endian::big)
        std::cout << "big-endian\n";
    else if constexpr (std::endian::native == std::endian::little)
        std::cout << "little-endian\n";
    else
        std::cout << "mixed-endian\n";
}
           

std::remove_cvref

去除cv和引用

#include <type_traits>
 
int main()
{
    static_assert(std::is_same_v<std::remove_cvref_t<int>, int>);
    static_assert(std::is_same_v<std::remove_cvref_t<int&>, int>);
    static_assert(std::is_same_v<std::remove_cvref_t<int&&>, int>);
    static_assert(std::is_same_v<std::remove_cvref_t<const int&>, int>);
    static_assert(std::is_same_v<std::remove_cvref_t<const int[2]>, int[2]>);
    static_assert(std::is_same_v<std::remove_cvref_t<const int(&)[2]>, int[2]>);
    static_assert(std::is_same_v<std::remove_cvref_t<int(int)>, int(int)>);
}
           

bind_front

和std::bind是一個系列的方法,bind_front可以綁定前n個參數而不用敲placeholder。

#include <functional>
#include <iostream>

int main()
{
    auto calc=[](int a, int b, int c) { return a+b-c;};
    
    auto aa = std::bind_front(calc, 1,2);
    std::cout << aa (3)<<"\n";
    auto bb = std::bind_front(calc, 1,2,3);
    std::cout << bb ()<<"\n";
    auto cc=std::bind(calc, 1,std::placeholders::_2,std::placeholders::_1);
    std::cout<<cc(3,2)<<"\n";   
    auto dd=std::bind(calc, std::placeholders::_1,std::placeholders::_2,3);
    std::cout<<dd(1,2); 
}
           

std::atomic_ref

原子引用

在下面的例子中,最終将列印100

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

int do_count(int value)
{
   std::atomic<int> counter { value };

   std::vector<std::thread> threads;
   for (int i = 0; i < 10; ++i)
   {
      threads.emplace_back([&counter]() {
         for (int i = 0; i < 10; ++i)
         {
            ++counter;
            {
               using namespace std::chrono_literals;
               std::this_thread::sleep_for(50ms);
            }
         }
      });
   }

   for (auto& t : threads) t.join();

   return counter;
}

int main()
{
   int result = do_count(0);
   std::cout << result << '\n'; // prints 100
}
           

std::atomic并作用于引用不生效, 下面的例子将列印0。

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

void do_count_ref(int& value)
{
   std::atomic<int> counter{ value };

   std::vector<std::thread> threads;
   for (int i = 0; i < 10; ++i)
   {
      threads.emplace_back([&counter]() {
         for (int i = 0; i < 10; ++i)
         {
            ++counter;
            {
               using namespace std::chrono_literals;
               std::this_thread::sleep_for(50ms);
            }
         }
         });
   }

   for (auto& t : threads) t.join();
}

int main()
{
   int value = 0;
   do_count_ref(value);
   std::cout << value << '\n'; // prints 0
}
           

使用atimic_ref進行修改,可以列印100。

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

void do_count_ref(int& value)
{
   std::atomic_ref<int> counter{ value };

   std::vector<std::thread> threads;
   for (int i = 0; i < 10; ++i)
   {
      threads.emplace_back([&counter]() {
         for (int i = 0; i < 10; ++i)
         {
            ++counter;
            {
               using namespace std::chrono_literals;
               std::this_thread::sleep_for(50ms);
            }
         }
         });
   }

   for (auto& t : threads) t.join();
}

int main()
{
   int value = 0;
   do_count_ref(value);
   std::cout << value << '\n'; // prints 0
}
           

std::map<Key,T,Compare,Allocator>::contains

map新增contains方法,在此之前使用的是find或者count。

#include <iostream>
#include <map>
 
int main()
{
    std::map<int,char> example = {{1,'a'},{2,'b'}};
 
    for(int x: {2, 5}) {
        if(example.contains(x)) {
            std::cout << x << ": Found\n";
        } else {
            std::cout << x << ": Not found\n";
        }
    }
}
           

std::barrier

線程屏障

#include <barrier>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
 
int main()
{
    const auto workers = { "Anil", "Busara", "Carl" };
 
    auto on_completion = []() noexcept
    {
        // locking not needed here
        static auto phase = "... done\n" "Cleaning up...\n";
        std::cout << phase;
        phase = "... done\n";
    };
 
    std::barrier sync_point(std::ssize(workers), on_completion);
 
    auto work = [&](std::string name)
    {
        std::string product = "  " + name + " worked\n";
        std::cout << product;  // ok, op<< call is atomic
        sync_point.arrive_and_wait();
 
        product = "  " + name + " cleaned\n";
        std::cout << product;
        sync_point.arrive_and_wait();
    };
 
    std::cout << "Starting...\n";
    std::vector<std::jthread> threads;
    threads.reserve(std::size(workers));
    for (auto const& worker : workers)
        threads.emplace_back(work, worker);
}
           

std::latch鎖存器

latch = single-use barrier.

#include <functional>
#include <iostream>
#include <latch>
#include <string>
#include <thread>
 
int main() {
    struct job {
        const std::string name;
        std::string product{"not worked"};
        std::thread action{};
    } jobs[] = {{"annika"}, {"buru"}, {"chuck"}};
    
    std::latch work_done{std::size(jobs)};
    std::latch start_clean_up{1};
    
    auto work = [&](job& my_job) {
        my_job.product = my_job.name + " worked";
        work_done.count_down();
        start_clean_up.wait();
        my_job.product = my_job.name + " cleaned";
    };
    
    std::cout << "Work starting... ";
    for (auto& job : jobs) {
        job.action = std::thread{work, std::ref(job)};
    }
    work_done.wait();
    std::cout << "done:\n";
    for (auto const& job : jobs) {
        std::cout << "  " << job.product << '\n';
    }
    
    std::cout << "Workers cleaning up... ";
    start_clean_up.count_down();
    for (auto& job : jobs) {
        job.action.join();
    }
    std::cout << "done:\n";
    for (auto const& job : jobs) {
        std::cout << "  " << job.product << '\n';
    }
}
           

std::counting_semaphore

計數信号量

#include <chrono>
#include <iostream>
#include <semaphore>
#include <thread>
 
// global binary semaphore instances
// object counts are set to zero
// objects are in non-signaled state
std::binary_semaphore
    smphSignalMainToThread{0},
    smphSignalThreadToMain{0};
 
void ThreadProc()
{
    // wait for a signal from the main proc
    // by attempting to decrement the semaphore
    smphSignalMainToThread.acquire();
 
    // this call blocks until the semaphore's count
    // is increased from the main proc
 
    std::cout << "[thread] Got the signal\n"; // response message
 
    // wait for 3 seconds to imitate some work
    // being done by the thread
    using namespace std::literals;
    std::this_thread::sleep_for(3s);
 
    std::cout << "[thread] Send the signal\n"; // message
 
    // signal the main proc back
    smphSignalThreadToMain.release();
}
 
int main()
{
    // create some worker thread
    std::thread thrWorker(ThreadProc);
 
    std::cout << "[main] Send the signal\n"; // message
 
    // signal the worker thread to start working
    // by increasing the semaphore's count
    smphSignalMainToThread.release();
 
    // wait until the worker thread is done doing the work
    // by attempting to decrement the semaphore's count
    smphSignalThreadToMain.acquire();
 
    std::cout << "[main] Got the signal\n"; // response message
    thrWorker.join();
}
           

string::starts_with / ends_with

字元串開頭/結尾

#include <iostream>
#include <string>
#include <string_view>
 
template<typename PrefixType>
void test_prefix_print(const std::string& str, PrefixType prefix)
{
    std::cout << '\'' << str << "' starts with '" << prefix << "': "
              << str.starts_with(prefix) << '\n';
}
 
int main()
{
    std::boolalpha(std::cout);
    auto helloWorld = std::string("hello world");
 
    test_prefix_print(helloWorld, std::string_view("hello"));
 
    test_prefix_print(helloWorld, std::string_view("goodbye"));
 
    test_prefix_print(helloWorld, 'h');
 
    test_prefix_print(helloWorld, 'x');
}
           

std::size

size的common方法

#include <iostream>
#include <vector>
 
int main()
{
    std::vector<int> v{3, 1, 4};
    std::cout << std::size(v) << '\n';
 
    int a[]{-5, 10, 15};
    std::cout << std::size(a) << '\n';
 
    // since C++20 the signed size (ssize) is available
    auto i = std::ssize(v);
    for (--i; i != -1; --i)
        std::cout << v[i] << (i ? ' ' : '\n');
    std::cout << "i = " << i << '\n';
}
           

std::is_bounded_array_v和std::is_unbounded_array

#include <iostream>
#include <type_traits>
 
#define OUT(...) std::cout << #__VA_ARGS__ << " : " << __VA_ARGS__ << '\n'
 
class A {};
 
int main()
{
    std::cout << std::boolalpha;
    OUT(std::is_bounded_array_v<A>);
    OUT(std::is_bounded_array_v<A[]>);
    OUT(std::is_bounded_array_v<A[3]>);
    OUT(std::is_bounded_array_v<float>);
    OUT(std::is_bounded_array_v<int>);
    OUT(std::is_bounded_array_v<int[]>);
    OUT(std::is_bounded_array_v<int[3]>);
}
           
#include <iostream>
#include <type_traits>
 
#define OUT(...) std::cout << #__VA_ARGS__ << " : " << __VA_ARGS__ << '\n'
 
class A {};
 
int main()
{
    std::cout << std::boolalpha;
    OUT( std::is_unbounded_array_v<A> );
    OUT( std::is_unbounded_array_v<A[]> );
    OUT( std::is_unbounded_array_v<A[3]> );
    OUT( std::is_unbounded_array_v<float> );
    OUT( std::is_unbounded_array_v<int> );
    OUT( std::is_unbounded_array_v<int[]> );
    OUT( std::is_unbounded_array_v<int[3]> );
}
           

std::erase_if

按照條件erase資料

#include <iostream>
#include <numeric>
#include <string_view>
#include <vector>
 
void print_container(std::string_view comment, const std::vector<char>& c)
{
    std::cout << comment << "{ ";
    for (auto x : c)
        std::cout << x << ' ';
    std::cout << "}\n";
}
 
int main()
{
    std::vector<char> cnt(10);
    std::iota(cnt.begin(), cnt.end(), '0');
    print_container("Initially, cnt = ", cnt);
 
    std::erase(cnt, '3');
    print_container("After erase '3', cnt = ", cnt);
 
    auto erased = std::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; });
    print_container("After erase all even numbers, cnt = ", cnt);
    std::cout << "Erased even numbers: " << erased << '\n';
}
           

對比之前的remove_if和erase

#include <iostream>
#include <vector>
#include <algorithm>

bool isEven(int n) // 是否是偶數
{
    return n % 2 == 0;
}

int main()
{
    std::vector<int> vecTest;
    for (int i = 0; i < 10; ++i)
        vecTest.push_back(i);

    for (int i = 0; i < vecTest.size(); ++i)
        std::cout << vecTest[i] << " ";
    std::cout << std::endl;

    // 移動元素
    std::vector<int>::iterator itor = std::remove_if(vecTest.begin(), vecTest.end(), isEven);

    // 檢視移動後的變化
    for (int i = 0; i < vecTest.size(); ++i)
        std::cout << vecTest[i] << " ";
    std::cout << std::endl;

    // 删除元素
    vecTest.erase(itor, vecTest.end());

    for (int i = 0; i < vecTest.size(); ++i)
        std::cout << vecTest[i] << " ";

    return 0;
}

           

Mathematical constants

c++20新增了一些數學常量

#include <numbers>
#include <iostream>

int main() {
    std::cout << std::numbers::log2e_v<double> << std::endl;
}
           

#std::midpoint

#include <cstdint>
#include <iostream>
#include <limits>
#include <numeric>
 
int main()
{
    std::uint32_t a = std::numeric_limits<std::uint32_t>::max();
    std::uint32_t b = std::numeric_limits<std::uint32_t>::max() - 2;
 
    std::cout << "a: " << a << '\n'
              << "b: " << b << '\n'
              << "Incorrect (overflow and wrapping): " << (a + b) / 2 << '\n'
              << "Correct: " << std::midpoint(a, b) << "\n\n";
 
    auto on_pointers = [](int i, int j)
    {
        char const* text = "0123456789";
        char const* p = text + i;
        char const* q = text + j;
        std::cout << "std::midpoint('" << *p << "', '" << *q << "'): '"
                  << *std::midpoint(p, q) << "'\n";
    };
 
    on_pointers(2, 4);
    on_pointers(2, 5);
    on_pointers(5, 2);
    on_pointers(2, 6);
}
           

std::lerp

線性計算

a+t(b-a)

#include <cassert>
#include <cmath>
#include <iostream>
 
float naive_lerp(float a, float b, float t)
{
    return a + t * (b - a);
}
 
int main()
{
    std::cout << std::boolalpha;
 
    const float a = 1e8f, b = 1.0f;
    const float midpoint = std::lerp(a, b, 0.5f);
 
    std::cout << "a = " << a << ", " << "b = " << b << '\n'
              << "midpoint = " << midpoint << '\n';
 
    std::cout << "std::lerp is exact: "
              << (a == std::lerp(a, b, 0.0f)) << ' '
              << (b == std::lerp(a, b, 1.0f)) << '\n';
 
    std::cout << "naive_lerp is exact: "
              << (a == naive_lerp(a, b, 0.0f)) << ' '
              << (b == naive_lerp(a, b, 1.0f)) << '\n';
 
    std::cout << "std::lerp(a, b, 1.0f) = " << std::lerp(a, b, 1.0f) << '\n'
              << "naive_lerp(a, b, 1.0f) = " << naive_lerp(a, b, 1.0f) << '\n';
 
    assert(not std::isnan(std::lerp(a, b, INFINITY))); // lerp here can be -inf
 
    std::cout << "Extrapolation demo, given std::lerp(5, 10, t):\n";
    for (auto t{-2.0}; t <= 2.0; t += 0.5)
        std::cout << std::lerp(5.0, 10.0, t) << ' ';
    std::cout << '\n';
}