竣工中: 现代 C++ 与高性能计算编程

现代 C++

CMake

add_library 可以导入导出链接库,导入时是别名

set(TARGET_MAIN main)

add_executable(${TARGET_MAIN} main.cpp)

find_package(OpenSSL REQUIRED)
if (OPENSSL_FOUND)
    # Add the include directories for compiling
    target_include_directories(${TARGET_MAIN} PUBLIC ${OPENSSL_INCLUDE_DIR})
    # Add the dynamic lib for linking
    target_link_libraries(${TARGET_MAIN} OpenSSL::SSL OpenSSL::Crypto)
    message(STATUS "Found OpenSSL ${OPENSSL_VERSION}")
else()
    message(STATUS "OpenSSL Not Found")
endif()

# OPENSSL_USE_STATIC_LIBS
# target_include_directories(${TARGET_MAIN} PUBLIC /usr/include/openssl)
# target_link_libraries(${TARGET_MAIN} crypto.a)

ldd -r

objdump -x / -T

addr2line

值类别

根据维基百科的描述,

  • 在 C 语言中,左值最初表示可以被赋值(即位于赋值运算符左侧)的对象;
  • 在 C++ 03 标准中,左值被规定成具有标识的表达式,而不具有标识的表达式规定为右值:
    • 对象名、指针、引用均为左值,具有确定的内存地址。
    • 字面量、临时对象为右值,右值仅可在创建它的表达式中被访问。
  • 在 C++ 11 标准中,引入 “右值引用类型” 与移动语义,把值类别扩充到:
    • 左值,具有标识,但不可以移动,也是 C++ 03 的经典左值。
    • 临终值,具有标识且可移动,代表对象接近生存期结束,其数据将被移走。
    • 纯右值,不具有标识,但可移动,像 i++ 返回的前值 i。

简而言之,左值是具有标识(变量名),默认只有拷贝语义,但可通过 std::move() 把左值修饰成临终值,具有可移动语义;右值不具有标识,是临时变量,像函数返回值(非引用类型),默认就具有可移动语义。

PS. 如果把 Spark RDD 的 Transform 与 Action 算子的语义加入到编程语言中,值类别分为创建物理视图类别、逻辑视图类别,感觉比左右值更先进?!

移动语义

同样的,根据维基百科的描述,C++ 作为追求执行效率的语言,用在临时对象或函数返回值给左值对象赋值时的深拷贝一直受到诟病。考虑到临时对象的生命期仅在表达式中持续,如果把临时对象的内容直接移动给被赋值的左值对象,效率改善将是显著的。这就是移动语义的来源。

在微软文档中,当类需要同时提供移动构造和移动赋值运算符(实际上,只要其中一个需要实现,按照 C++ 编程规范三五法则,两者都需要实现),可在移动构造函数中调用移动赋值,减少冗余代码。

// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept
   : _data(nullptr)
   , _length(0)
{
   *this = std::move(other);
}

万能引用

  • 要说万能引用之前,不得不提的是 —— 引用折叠,引用折叠常见于函数模板,并且函数的形参必须是右值引用(T&&),函数模板将依照传入对象的性质推断模板参数类型。
    • 折叠规则是只要存在左值引用,均折叠为左值引用。
    • 比如传入的是左值,推断出 T&(实际是 T& &&),而传入的是右值,则推断出 T。
  • 万能引用,正是利用函数模板的类型推断及引用折叠,让函数即可接收左值也可接收右值。注意,万能引用只能用在函数模板

完美转发

  • 完美转发,std::forward<T>(),抽象的语义就是接收的左值或右值,保留其属性传递到下一个函数;用在万能引用的函数内,最终接收函数建议分两类,一类是右值引用函数签名的,另一类是左值引用函数签名的。
  • STL 大量使用万能引用与完美转发,使其可以保留左右值属性,进而保留移动语义。(可见得,这种设计模式首先是依赖函数模板,然后通过 std::forward<T>() 转发给下游接收函数)
// Vector impl.
template <class _Tp, class _Allocator>
template <class... _Args>
vector<_Tp, _Allocator>::emplace_back(_Args&&... __args)
{
    if (this->__end_ < this->__end_cap())
        __construct_one_at_end(_VSTD::forward<_Args>(__args)...);
    else
        __emplace_back_slow_path(_VSTD::forward<_Args>(__args)...);
    return this->back();
}

待补充。

template<class T>
void rvfnc(T&& val)
{
    T t = val;
    ++t;
    if (val == t) 
        std::cout << "rvfnc: this is lvalue." << std::endl;
    else 
        std::cout << "rvfnc: this is rvalue." << std::endl;
}

template<class T>
void rvfnc(T& val)
{
    std::cout << "rvfnc(overload): this is lvalue." << std::endl;
}

template<class T>
void univref(T&& val)
{
    rvfnc(std::forward<T>(val));
}

int main()
{
    int i = 10;
    univref(i);   // 等价于 rvfnc(i);
    univref(10);  // 等价于 rvfnc(10);  forward 实现原理就和 rvfnc 一样
    return 0;
}

三五法则

  1. 自定义析构函数的类,将自动禁用默认的拷贝函数与移动函数。
    因此,在自定义了析构函数的类上,实现可拷贝或可移动,需要手动编写其函数,或者显式指定 = default建议设计之初就显式声明)。
  2. 拷贝赋值 = 先析构当前 + 拷贝构造。
    当需要拷贝构造时,考虑到 RAII 及可能的延迟初始化,拷贝构造应该与拷贝赋值联动。
    同理,移动构造与移动赋值应该联动。
  3. 多态基类的析构函数必须设置为虚函数。

类型推导

函数模板可以对形参推导(模板实例化),

auto 通过表达式求值后推导返回值类型,

dectype 通过上下文推导类型,因此将包含详细的引用 & 及 const 信息。

智能指针

std::unique_ptr 没啥,利用 RAII

std::shared_ptr 控制块 shared_from_this

CRTP 与静态多态

enable_shared_from_this 就是静态多态模式下的 hook,在创建 shard_ptr 时候,初始化对象内指向 this 的 weak_ptr

std::weak_ptr lock 快照创建新的临时的 shared_ptr

Lambda 函数

捕获陷阱,可能使用捕获了已释放的栈变量的 Lambda、可能往某个类中注册了捕获 this 指针修饰的已释放成员对象(虚假的值捕获)。

初始化捕获。捕获移动语义,以及 auto 形参使用 dectype作为完美转发的模板类型。

并发 API

magic static

异步任务建议指定 std::launch::async 以禁止默认额外使用的 std::launch::deferred 惰性求值特性

处于 Joinable 线程析构将会使进程收到 SIGABRT 信号而异常终结退出。

join/detach 怎么反应在 task_struct?

async 针对 thread+promise+future+packaged_task 的封装实现

async 析构函数的隐式 join,下面代码即便没有 wait for ready 或者 get(get 本身也调用 wait),也会执行,并且阻塞当前线程,也就是 join 行为。

    {
        std::future<int> f2 = std::async(std::launch::async, []{
            std::this_thread::sleep_for(std::chrono::duration<int>(10));
            return 1;
        });
    }

实现 LinkedBlockingQueue

内存模型

SPDK/DPDK

并行编程

SIMD

https://mp.weixin.qq.com/s/uBXragtnbPdMRZ379RajKw

TBB

openMP

CUDA

集群并行编程

openMPI

OpenSHMEM

汇编的语义

https://godbolt.org/

HPX

https://github.com/STEllAR-GROUP/hpx

另一坑,陈希孺文集:数理统计学教程

参考资料

  1. https://cmake.org/cmake/help/latest/command/add_library.html
  2. https://oneraynyday.github.io/dev/2020/07/03/Value-Categories/
  3. https://stackoverflow.com/questions/70159423/forward-with-remove-reference-in-template-function-parameter-type
  4. https://developer.nvidia.com/zh-cn/blog/multi-gpu-programming-with-standard-parallel-c-part-1/
  5. https://github.com/CnTransGroup/EffectiveModernCppChinese
  6. https://mp.weixin.qq.com/s/uBXragtnbPdMRZ379RajKw

发表评论

您的电子邮箱地址不会被公开。