首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Modern c++快速浅析

Modern c++快速浅析

作者头像
高性能架构探索
发布于 2024-01-03 07:56:05
发布于 2024-01-03 07:56:05
57400
代码可运行
举报
文章被收录于专栏:技术随笔心得技术随笔心得
运行总次数:0
代码可运行

模板类型推导

模板类型推导中最重要的是弄清它什么时候会抛弃引用,什么时候会抛弃常量性

template<typename T> void func(T& param);在这个示例函数中,如果传递进是一个const int&的对象,那么T推导出来的类型是const intparam的类型是const int&。可见引用性在型别推导的过程中被忽略•template<typename T> void func(T param);在这个示例函数中,我们面临的是值传递的情景,如果传递进的是一个const int&的对象,那么Tparam推导出来的类型都是int如果传递进的是一个const char* const的指针,那么T和param推导出来的类型都是const char*,顶层const被忽略。因为这是一个拷贝指针的操作,因此保留原指针的不可更改指向性并没有太大的意义

auto

大多数情况下auto推断出来的结果和模板类型推导的结果是一样的,不同点在于对大括号初始物的处理

值与指针等推导

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const int a = 10;
// b为 non-const int类型 这里相当于"拷贝"
auto b = a;
// 明确指明cb的常量性
const auto cb = a;

auto一般情况下会忽略顶层const,保留底层const(顶层const:指针本身是常量,底层const:指针所指对象是常量),这点与模板类型推导一致

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int* const apc = &a;
const int* acp = &a;
// p为 int*类型, 顶层const被忽略
auto p = apc;
// cp为 const int*类型,底层const被保留
auto cp = acp;

std::initializer_list的推导

auto推导具有将大括号初始物转换为std::initializer_list<T>T类型的数据的能力,而模板类型推导不具备这样的能力

C++14中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto a{ 1, 2, 3 };        // std::initializer_list
auto b{ 1 };            // std::initializer_list

C++17中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto a{ 1, 2, 3 };    // 非法
auto b{ 1 };    // int
auto c = { 1, 2, 3 };    // 与C++14相同,皆为std::initializer_list
auto d = { 1 };    // 与C++14相同,皆为std::initializer_list

返回值推导

将函数的返回值标记为auto,意味着返回值类型的推导遵循模板类型推导的原则,而非auto的推导原则

C++11中加入的_trailing return type_(尾返回类型),需要搭配decltype使用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename T1, typename T2>
auto MathPlus(T1 a, T2 b) -> decltype(a + b)
{
    return a + b;
}

C++14中,可以省略decltype

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename T1, typename T2> auto MathPlus(T1 a, T2 b) { return a + b; }
int main() { 
  std::cout << MathPlus(1 + 2.34) << std::endl; 
}

但这里需要注意的是,返回值类型推导遵循的是模板类型推导的原则,因此对于大括号初始物而言,没有办法正确推导

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 无法通过编译
auto func() {
    return {1, 2, 3};
}

但我们更应该知道,即使这能通过编译,被推导为std::initializer_list<int>,我们仍然应该避免返回一个局部的std::initializer_list<int>,因为它是指向栈上的数据,离开函数作用域后再访问将会出现不确定的结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 应该避免这样做
std::initializer_list<int> func() {
    return {1, 2, 3};
}

Lambda表达式推导

在C++11中,Lambda表达式的参数需要具体的类型声明

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto MyLambda = [](int a, int b) { return a + b; };

auto用于Lambda表达式时,同样代表遵循模板类型推导的原则,例如C++11中可以将其用于匿名函数参数的推导

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 使用auto接住匿名函数,匿名函数使用auto进行参数推导,匿名函数的返回值使用auto推导 
auto MyLambda = [](auto a, auto b) { return a + b; };

由于它也是遵循模板类型推导的原则,因此对于大括号初始物而言,参数还是返回值都无法正确的将其推导出来

Range-base-loop with auto

参考自知乎-蓝色-range-base-loop中使用auto

总结:

•当你想要拷贝range的元素时,使用for(auto x : range)•当你想要修改range的元素时,使用for(auto&& x : range)•当你想要只读range的元素时,使用for(const auto& x : range)

template

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<int data>
template<double data>
// ...

对于非模板类型参数而言,使用auto进行自动推断会方便很多

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<auto param>

auto是可选项而不是必选项

•对于部分情景而言,使用auto能够避免不少低级错误,如Effective Modern C++中提到的std::vector<int>::size_typestd::unordered_map中的键值对例如std::pair<const std::string, int>•但是过量使用auto会导致代码的可读性降低;同时由于是编译器自动推导,各种类型忽略问题以及转换问题我们都需要重视以std::vector<bool>为例,std::vector<bool>std::vector的一个特化版本,容器中的bool值是1bit1bit存储的。与STL中的其他容器不同,std::vector<bool>::operator[]返回的不是bool&,而是返回std::vector<bool>::reference,这个reference能够转换为bool类型的值std::vector<bool> result = {true, false, true}; bool value1 = result[0]; // 此时value2被推导为std::vector<bool>::reference,而不是bool auto value2 = result[1];我们能对其做出强转来修复这个问题auto value3 = static_cast<bool>(result[2]);

[c++中为什么不提倡使用vector?]

decltype

autodecltype都是C++11引入的类型推导。decltype能够从表达式中推断出要定义的变量类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
decltype(a + b) i; //假设a是int而b是double,那么i的类型就是表达式(a + b)的类型,即double

•当decltype处理变量时,它与auto不同,并不会去忽略掉顶层const,原变量是啥它就是啥•当decltype处理函数时,它只是获取函数的返回值类型,并不会去调用函数•当decltype处理表达式时,假设类型为Tstd::string name = "Mikasa"; std::string& nr = name, *np = &name; decltype(name) d0; // string // 任何在name之上叠加符号的左值表达式都将被推断为引用类型 decltype((name)) d1; // string&,ERROR,未初始化的引用 decltype(*(&name)) d2; // string&,ERROR,未初始化的引用 decltype(std::move(name)) d3; // string&&,ERROR,未初始化的引用 decltype(*np) d4; // string&,ERROR,未初始化的引用 decltype(nr + 0) d5; // string•若表达式的值类型为纯右值,则推导出T•若表达式的值类型为左值:若表达式只是变量名,则推导出T;其他情况推导出T&•若表达式的值类型为将亡值,则推导出T&&•当decltype处理Lambda表达式时auto f = [](int a, int b) { return a + b; }; // decltype(f) g = [](int a, int b) { return a * b; }; // ERROR decltype(f) g = f; // OK即使是完全相同的返回值和函数参数类型,但是编译器仍然会报错,因为每一个Lambda类型都是独有且无名的。

decltype(auto)

上文中提到auto作为返回值时将采用模板类型推导的规则,正因为如此它可能会遗失一些我们需要的类型(如引用或常量性),这个时候就需要使用decltype(auto)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
    authenticateUser();
    return c[i];
}

std::deque<int> d;
// ...
authAndAccess(d, 5) = 10;

std::deque<int>::operator[]的重载将会返回int&,但是由于使用模板类型推导,返回值的类型将会是int,而在C++中对右值进行赋值是非法的,因此会编译失败。对此能有两种做法

template<typename Container, typename Index> auto& authAndAccess(Container& c, Index i);template<typename Container, typename Index> auto& authAndAccess(Container& c, Index i);

typedef和using

using是C++11加入的,它叫做_alias declaration_(别名声明)。在拓展typedef的同时也让C++的C++味儿更浓了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef int Status;
using Status = int;

回归主题,在一些十分复杂的名称面前,我们会选择取别名,比如

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef std::vector<std::pair<std::string, std::function<void(int)>>> Selection;
using Selection = std::vector<std::pair<std::string, std::function<void(int)>>>;    //两种方法等效

使用using会令代码的可读性更高一些,以函数指针为例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 令MyFunc为void(*)(int, int)类型
typedef void(*MyFunc)(int, int);
using MyFunc = void(*)(int, int);
// 成员函数指针
using MyClassFunc = void(MyClass::*)(double, std::string)

除此之外,using能更方便的为模板取别名(alias templates),这是typedef无法轻易做到的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename U>
class MyAlloc {};

template<typename T, typename U>
class MyVector {
    T data;
    U alloc;
};

template<typename T>
using vec = MyVector<T, MyAlloc<T>>;

vec<int> v;            // 等效于MyVector<int, MyAlloc<int>> v;

除此之外,using还可用于解决由于privateprotected继承导致子类的对象无法访问父类中成员的问题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Base
{
public:
    int data;
    void UseData() {}
    void HandleData() {}
};

class Derived : private Base
{
    
};

Derived d; // 无法访问Base中的对象

但是在子类中添加了对父类对象的using后

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Derived : private Base
{
// 一定需要是public属性
public:
    using Base::data;
    using Base::UseData;
};

auto d = Derived().data;
Derived().UseData();
// 无法调用到HandleData

typename

对于刚学习C++不久的人来说,最常见的typename的使用场所就是模板了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename T> 
template<class T>

上例中typenameclass并无任何差别。初学者选择typename可能会对模板有更好的了解(毕竟若模板传进来的是int,它是内置类型,看起来不是一个class

进入正题,使用typename可以明确的告诉编译器,后面跟着的这个名字是类中的类型成员,而不是数据成员(例如静态成员变量)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Foo {
public:
    typedef int FooType;
    int f = 10;
};
class Bar {
public:
    static int b;
};
int Bar::b = 10;

template<typename Param, typename Value>
class MyClass {
public:
    Foo::FooType mycData1 = 10;                // 直接使用Foo中的类型
    typename Param::FooType mycData2 = 10;    // 需加typename以指明这是一种类型
private:
    int mycData3 = Value::b;            // 直接使用Bar中的成员
};

这里贴一个简单的类型萃取的函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename T>
class MyClass {
public:
    using value_type = T;
};
template<typename T>
void MyFunc(const T& t)
{
    typename T::value_type data;    // 定义一个类型与参数的模板参数相同的变量data
    std::cout << typeid(data).name() << std::endl;
}
int main()
{
    MyClass<int> myc;
    MyFunc(myc);
}

typedef与typename

给模板类__type_traits<T>中的has_trivial_destructor类型取别名,叫做trivial_destructor

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
using trivial_destructo = typename __type_traits<T>::has_trivial_destructor;    // C++11的写法

给模板类Registration<PointSource, PointTarget>中的PointCloudSource类型取别名,叫做PointCloudSource

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef typename Registration<PointSource, PointTarget>::PointCloudSource PointCloudSource; using PointCloudSource = typename Registration<PointSource, PointTarget>::PointCloudSource; // C++11

template消歧义符

typename类似,template修饰代表告诉编译器它后面的东西是模板类或模板函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Array {
public:
    template <typename T>
    struct InArray { typedef T ElemT; };
};

template <typename T>
void Foo(const T& arr) {
    // typename T::InArray<int>::ElemT num;            // 编译时报错,详见下图
    typename T::template InArray<int>::ElemT num;
}

知乎-C++ 为什么有时候必须额外写 template?

enum class

普通的枚举类型是不限定作用域的,即在同一个namespace中,是不能出现重名的,且能够被隐式转换为int等类型的值

;强枚举类型(enum class)的枚举类型是唯一的,但仍可以显示强转为intunsigned int等类型

强枚举类型默认底层是int,但是也可以自行指定

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 该枚举的大小是8字节
enum class TestEnum : int64_t {
};

可以通过std::underlying_type来获取强枚举的底层类型。因为UserInfoFields底层是std::size_t,所以这个模板函数将会返回std::size_t类型的值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum class UserInfoFields : std::size_t { uiName, uiEmail, uiReputation };

template<typename E>
constexpr auto toUType(E enumerator) noexcept {
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

noexcept

•大多数函数都是异常中立的,此类函数自身并不会抛出异常,但是它们调用的函数可能会抛出异常。异常中立函数永远不具备noexcept的性质•noexcept性质对于移动操作,swap,内存释放函数和析构函数最有价值

C++11的noexcept标识符与操作符应如何正确使用?

constexpr

constexpr代表编译期常量,它所标识的值可能被放入到只读内存段中,如数组,非类型模板参数,枚举类型等要求的都是编译期常量,const代表运行期常量。所有constexpr对象都是const对象,但并非所有的const对象都是constexpr对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 都要求编译期常量
int arr[10];
std::array<int, 5> arr;

constexpr的泛用性在每代C++中都得到了提高

在C++11中,constexpr可以用来修饰对象(包括内置类型和自定义类型),以及可以用来修饰函数(构造函数,成员函数,普通函数等等),如果以constexpr修饰构造函数,那么代表构造出来的对象可以是一个编译期常量

以修饰函数为例,函数是否的返回值是否满足constexpr取决于两个方面

•传入的参数是否是编译期常量•函数体内的计算是否是编译期能够处理的

当两者条件都能满足时,它的结果就是constexpr的,否则它的运作方式和普通函数无异(编译器不对constexpr做处理)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constexpr int pow(int base, int exp) noexcept {
    return (exp == 0 ? 1 : base * pow(base, exp - 1));
}

在C++14中,对constexpr修饰的函数做了进一步的拓展,C++14中的constexpr函数不再是只能单纯的包含一条return语句了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constexpr int pow(int base, int exp) noexcept {
    int result = 1;
    for (int i = 0; i < exp; i++)
        result *= base;
    return result;
}

而且在C++14中,constexpr也能用于修饰setter函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public:
    constexpr void setX(double newX) noexcept {
        x = newX;
  }

在C++17中,新增了constexpr的用途,可以用在_if-else_语句中,称作_if-constexpr_,常用于模板元编程中。

Lambda表达式

Lambda表达式其实是块语法糖,其结构如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[函数对象参数](函数参数列表) mutable throw(类型)->返回值类型 { 函数语句 };

•当捕获的是this时,lambda函数体中与其所在的成员函数有着相同的protectedprivate访问权限•除了引用捕获外,其他各种捕获都会默认加上const修饰符,mutable关键字可以解决这个问题(如果匿名函数体中发生对按值捕获的变量的修改,那么修改的是拷贝而不是值本身)void func(int& num) { } void const_capture() { int data = 20; // 编译出错 无法将const int绑定到non-const-reference的函数参数上 callBack = [=]() { func(data); }; }•当明确Lambda表达式不会抛出异常时,可以使用noexcept修饰[]() noexcept { /* 函数语句 */ }•当Lambda表达式没有捕获任何参数时,它可以转换成为一个函数指针•Lambda中可以直接使用静态变量以及全局变量,不存在捕获的行为。也正因为此当调用Lambda时对该数据的访问是该数据当前的数值

Constexpr Lambda

此功能需要开启_std:c++17_

显式constexpr

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto lambda = [](int num) constexpr { return num + 10; };
int arr[lambda(10)];

隐式constexpr

当Lambda满足constexpr条件时,会自动隐式声明其为constexpr。也就是说上面那个例子其实不加constexpr也可以

当Lambda转换成函数指针时,需要显式指明函数指针为constexpt

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constexpr int(*pFunc)(int) = lambda;
int arr[pFunc(100)];

捕获生命周期

C++中其实并没有闭包的概念,更准确的应该将lambda划分为带捕获的lambda以及不带捕获的lambda

在C#这种具备GC机制的语言中,闭包能够延长捕获的变量的生命周期(理解为能够延长生命周期的按引用捕获)

而C++中的按引用捕获并不能延长对象的生命周期,且按引用捕获会导致lambda表达式包含了对局部对象的引用,这很可能会导致空悬引用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::function<void()> callBack;

void pass_vector(const std::vector<int>& vec) {
    std::cout << vec[0] << std::endl;
}

void create() {
    std::vector<int> data(10, 20);
    callBack = [&]() { pass_vector(data); };
}

int main() {
    create();
    // 访问得到不确定的值
    callBack();
}

常见的解决方法是使用值捕获,或者使用捕获指向堆上的指针来自行管理对象的生命周期(或者使用智能指针,注意std::shared_ptr按引用捕获的时候,不会累加引用次数)

但按值捕获也不一定能保证悬垂安全,例如对this指针的捕获

初始化捕获

初始化捕获是C++14中引入的新特性,解决了C++11中无法“移动捕获”的问题(可以理解为是为Lambda生成的匿名类创建并初始化类成员)

假设有一个不可拷贝的对象需要被捕获进Lambda表达式中,那么C++14中就可以这么做

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::unique_ptr<int> uniquePtr = std::make_unique<int>();
// 对uniquePtr执行各种操作
// 将其捕获进Lambda中
auto lambda = [anotherPtr = std::move(uniquePtr)]() { /* */ };

而在C++11中,只能通过在Lambda外再包装一层std::bind的方式来解决

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::vector<double> data;
// Codes...
auto lambda = std::bind([](const std::vector<double>& _data) { /* */ }, std::move(data));

除了“移动捕获”外,还可以利用初始化捕获来初始化Lambda表达式中所需要使用的变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto lambda = [uniquePtr = std::make_unique<int>()]() { /* */ };

捕获 *this

默认情况下,使用[=]能够默认捕获this指针,能够在lambda中修改或访问类成员

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {
public:
    int data = 10;
    void test_lambda() {
        auto lambda = [=]() { data = 200; };
        // data的值将会被修改为200
        lambda();
    }
};

或者显式指明捕获this指针,也是能够修改和访问类成员

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto lambda = [this]() { data = 200; };

但是上述两者都是对指针的捕获,因此具有lambda表达式调用时期与this指针的生命周期问题。若lambda表达式的生命周期比this指针更长,那么就会发生对野指针的访问

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::function<void()> funcObj;

struct My_Struct
{
    int data = 20;
    void record() { funcObj = [this]() { std::cout << data << std::endl; }; }
};

int main()
{
    {
        std::unique_ptr<My_Struct> uniquePtr = std::make_unique<My_Struct>();
        uniquePtr->record();
    }
    // 出现不确定的结果
    funcObj();
}

为了解决生命周期的问题,可以使用初始化捕获或者捕获*this

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct My_Struc {
    int data = 20;
    // 使用初始化捕获
    void record() { funcObj = [_data = this->data]() { std::cout << _data << std::endl; }; }
};

如果捕获的是*this,那么Lambda会存在这整个类的副本,一切访问和修改都是发生在这个副本上的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct My_Struct
{
    int data = 20;
    // C++17中捕获*this
    void record() {
        funcObj = [*this]() { std::cout << data << std::endl; }; 
    }
};

捕获的是*this时,捕获的类型是const T,即匿名函数体中只能调用到常函数,如果想调用其他成员函数,需要加mutable修饰(修改变量同理,需要使用mutable修饰)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {
public:
    void member_func() {}
    void test_lambda() {
        auto lambda = [*this]() mutable { member_func(); };
    }
};

Lambda Capture of *this

lambda的大小

Lambda的大小主要看两个方面

•是否使用了捕获•如果使用了捕获,函数体中是否有使用到捕获的变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct My_Struct {
private:
    int data = 20;
    double pi = 3.14;
public:
    void func() {
        char localData = 'a';
        // arr不被使用 不列入计算
        int arr[30];
        // 该lambda的大小是8
        auto lambda = [=]() { return localData + data + pi; };
    }
};

如上方的代码,使用[=]进行值捕获,由于Lambda函数体中使用到了localDatadatapi。那么我们可以认为这个Lambda所生成的匿名类中,含有一个char类型和一个指针类型(this指针),由于内存对齐的缘故,这个Lambda类型的大小是8个字节

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 假设这是编译器生成的匿名类
class lambda_class {
private:
    char _localData;
    My_Struct* _pointer;
public:
    auto operator()() {
        return _localData + _pointer->data + _pointer->pi;
    }
};

不带捕获的Lambda可以看作是空类,不携带上下文信息,因此大小是1个字节

nullptr和NULL

NULL是一个宏,它代表了字面值0,它的类型是int

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

C++中把NULL定义为0的原因是:C++中不允许void*指针隐式转换为其他指针类型,即下面代码是非法的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int* p = (void*)0;

nullptr是C++11中的一个关键字,它的类型是std::nullptr_t

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#ifdef __cplusplus
    namespace std
    {
        typedef decltype(__nullptr) nullptr_t;
    }

    using ::std::nullptr_t;
#endif

default和delete

C++11前利用private以阻止访问成员函数,并且不给出它们的实现,如果在用户代码中仍然去访问此没有实现的成员函数,那么会在链接阶段得到错误。C++11后若访问到已delete的函数,那么会在编译阶段就得到错误,将错误诊断提前了

= delete 可以用来修饰任何函数,包括非成员函数和模板具现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename T>
void processPointer(T* ptr) {}

// 通过模板特化来删除指定的实现
template<>
void processPointer<void>(void*) = delete;

template<>
void processPointer<char>(char*) = delete;

= default只能用在特定的成员函数中,显式要求编译器生成对应版本的函数

override和final

若函数被override修饰,那么编译器将会严格检查改函数各部分是否满足重写的要求。该关键字用于减少程序员犯错

final代表终止继承链,若类或函数被final修饰,那么子类将无法再继承或再重写

以上

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能架构探索 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[Effective Modern C++(11&14)]Chapter 1: Deducing Types
1. Understand template type deduction. 函数模板的原型 template<typename T> void f(ParamType param); ParamType是一个左值引用或者指针时 template<typename T> void f(T& param); int x = 27; const int cx = x; const int& rx = x; f(x);// T是int,param类型是int& f(cx);// T是const int, p
昊楠Hacking
2018/05/26
6490
《Effective Modren C++》 进阶学习(上)
  作为一名有追求的程序猿,一定是希望自己写出的是最完美的、无可挑剔的代码。那完美的标准是什么,我想不同的设计师都会有自己的一套标准。而在实际编码中,如何将个人的标准愈发完善,愈发得到同事的认可,一定需要不断积累。如何积累,一定是从细微处着手,观摩优秀的代码,学习现有的框架,汲取前人留下的智慧。
开源519
2023/11/15
5300
《Effective Modren C++》 进阶学习(上)
【笔记】C++2.0新特性
本篇是这段时间看的侯捷关于C++的课程《C++2.0新特性》的笔记,课程内容大家自己找吧。这个课程主要是我用来回顾C++11的特性和拾遗的,因此笔记中只记录了我认为课程中比较重要的内容。这门课的很多内容都来自《C++标准库》和《Modern Effective C++》,在看了在看了。
ZifengHuang
2022/03/04
1K0
【笔记】C++2.0新特性
Effective Modern C++翻译(3)-条款2:明白auto类型推导
条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一样的,但是为什么会这样呢?模板的类型推导涉及了模板,函数和参数,但是auto的类型推导却没有涉及其中的任何一个。 这确实是对的,但这无关紧要,在auto类型推导和template之间存在一个直接的映射,可以逐字逐句的将一个转化为另外一个。 在条款1中,模板类型推导是以下面的模板形式进行举例讲解的: templa
magicsoar
2018/02/06
7830
[Effective Modern C++(11&14)]Chapter 3: Moving to Modern C++
1. Distinguish between () and {} when creating objects C++11中,初始化值的指定方式有三种:括号初始化,等号初始化和花括号初始化;其中花括号初始化是为了解决C++98的表达能力而引入的一种统一初始化思想的实例。 等号初始化和花括号初始化可以用于非静态成员变量的初始化 class Widget { ... private: int x {0}; // ok int y = 0; // ok int z(0);
昊楠Hacking
2018/05/26
1.9K0
Effective Modern C++翻译(4)-条款3:了解decltype
条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结果也会让你挠头思考,开始找一些参考资料进行研究,或是在网上寻找答案。 我们从典型的例子开始,因为它的结果都是在我们预料之中的,和模板类型推导与auto类型推导相比(参见条款1和条款2),decltype几乎总是总是返回变量名或是表达式的类型而不会进行任何的修改 const int i = 0;
magicsoar
2018/02/06
8840
C++ 的发展
C++ 是由 Bjarne Stroustrup 于 1979 年在贝尔实验室(Bell Labs)开始开发的,最初是作为 C 语言的一个扩展,目的是在不丧失 C 语言高效性的基础上,提供面向对象编程的特性。C++ 的发展历程可以分为以下几个重要阶段:
ljw695
2024/11/15
1.2K0
C++ 的发展
《Effective Modern C++》读书笔记
Note:为避免各种侵权问题,本文并没有复制原书任意文字(代码除外,作者已经声明代码可以被使用)。需要原书完整中文翻译的读者请等待官方译本的发布。
bear_fish
2018/09/19
1.9K0
【c++11】列表初始化与声明
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定:
用户11029103
2025/01/19
2890
【c++11】列表初始化与声明
C++11相较于C++98的核心提升与实现原理深度解析
在C++98标准中,类型声明的冗余性给开发者带来了显著的代码负担。以标准容器迭代器的声明为例,std::vector\<int>::iterator it = vec.begin()这样的语句要求开发者显式写出冗长的类型名称,不仅增加了代码量,也降低了可读性和开发效率。C++11引入的自动类型推导机制从根本上改变了这一现状,通过auto关键字实现了类型的隐式声明,使得上述代码可简化为auto it = vec.begin(),大幅提升了代码的简洁性。
码事漫谈
2025/07/12
1340
C++11相较于C++98的核心提升与实现原理深度解析
[Effective Modern C++(11&14)]Chapter 6:Lambda Expressions
1.The vocabulary associated with lambdas lambda expression 仅仅是一个表达式,是源码中一部分。 closure 是由一个lambda产生的运行时对象。 closure class 是一个类类型,一个closure可以从该closure class中实例化。每个lambda都会使得编译器产生一个独一无二的closure class。一个lambda内的语句会变成它的closure class的成员函数中可执行的指令。 2. Avoid defau
昊楠Hacking
2018/05/26
1.9K0
C++20新特性个人总结
concept乃重头戏之一,用于模板库的开发。功能类似于C#的泛型约束,但是比C#泛型约束更为强大。
用户7886150
2021/02/04
2.1K0
C++11常用新特性快速一览
在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。
f_zyj
2019/05/27
2.8K0
c++17好用的新特性总结
最近很火的鸿蒙系统框架代码,很多就是基于c++做的底层、运行时和应用框架封装。c++11后续又有11,14,17,20等众多新版本。哪个是主流?截至目前来说,c++17应该是新项目的首选。C++14在11的基础上查缺补漏,并未加入许多新特性,而C++17作为C++11后的第一个大版本,标志着现代C++逐渐走向成熟。
杨永贞
2022/05/25
3.8K0
c++17好用的新特性总结
C++14新增特性汇总
C++14整体来说只是发行的一个小版本,在C++11大版本的基础上做了一些优化和缺陷的修复。C++14在2014年8月18日正式批准宣布,同年12月15日正式发布release版本。本文中将就变动部分做一个总结,有需要改进和提升的地方希望大家批评指正。
CPP开发前沿
2021/11/16
5750
c++11类型推导
1 ParamType是一个指针或者引用(非通用universal reference引用)
江上摆渡翁
2020/07/09
5860
【翻译】C++14的新特性简介
之前写完了《C++Primer》的笔记,但是《C++Primer》已经是快十年的老书了,其包含的C++特性仅仅到C11为止,因此又去看了些C++14的特性,发现Anthony Calandra在https://github.com/AnthonyCalandra/modern-cpp-features/blob/master/CPP14.md 中有对C++14重要的新特性的简介,看完就翻译整理后发上来了。原文中有些地方写得不是很好理解所以对其做了少量修改。
ZifengHuang
2020/07/29
4.2K0
【翻译】C++14的新特性简介
C++11新特性学习笔记
C++11标准为C++编程语言的第三个官方标准,正式名叫ISO/IEC 14882:2011 - Information technology – Programming languages – C++。在正式标准发布前,原名C++0x。它将取代C++标准第二版ISO/IEC 14882:2003 - Programming languages – C++成为C++语言新标准。
CtrlX
2023/03/13
2.5K0
C++11新特性学习笔记
C++11新关键字
auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用。在C++11中被赋予了新的含义和作用,用于类型推断。
恋喵大鲤鱼
2019/02/22
3.3K0
C++11——引入的新关键字
auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用。在C++11中被赋予了新的含义和作用,用于类型推断。
恋喵大鲤鱼
2018/08/03
1.6K0
相关推荐
[Effective Modern C++(11&14)]Chapter 1: Deducing Types
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档