c++ 中的一些关键字:explicit, delete, default, override, final, noexcept

c++ 中的一些关键字:explicit, delete, default, override, final, noexcept 1 explicitexplicit 的意思是清楚的明显的。一般用在类的构造函数中防止隐式转换。在实际使用中建议使用explicit修饰构造函数保持清晰的语义。explicit specifier - cppreference.com如下代码(1) 类 A 的两个构造函数都没有使用 explicit 修饰所以如下两行代码隐式转换是允许的A a1 1;A a4 {4, 5};(2) 类 B 的两个构造函数都使用 explicit 修饰了不允许隐式构造所以下边两行代码编译不通过B b1 1;B b4 {4, 5};(3) 类 B如下代码使用强制类型转换是可以的。B b5 (B)1;#include iostream #include string class A { public: A(int) { } // converting constructor A(int, int) { } // converting constructor (C11) }; class B { public: explicit B(int) { } explicit B(int, int) { } }; int main() { A a1 1; // OK: copy-initialization selects A::A(int) A a2(2); // OK: direct-initialization selects A::A(int) A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int) A a4 {4, 5}; // OK: copy-list-initialization selects A::A(int, int) A a5 (A)1; // OK: explicit cast performs static_cast // B b1 1; // error: copy-initialization does not consider B::B(int) B b2(2); // OK: direct-initialization selects B::B(int) B b3(4, 5); // OK: direct-list-initialization selects B::B(int, int) // B b4 {4, 5}; // error: copy-list-initialization does not consider B::B(int, int) B b5 (B)1; // OK: explicit cast performs static_cast return 0; }显式的、隐式的explicit、implicit类似于同步和异步sync和async。这两对单词在代码命名中是很常见的。2 delete指定某个函数禁用。在单例模式中一个类只允许创建一个对象同时也不允许这个类进行拷贝构造或者对象之间进行赋值。怎么做到不让类进行拷贝构造以及不让对象之间进行赋值呢这个时候就可以使用 delete 来修饰拷贝构造函数和赋值运算符。因为只能有一个实例所以不能拷贝只能移动支持拷贝的话就不能保证单例了。如果在代码中使用了禁用的函数那么编译的时候会报错。#include iostream #include mutex class Test { public: static Test *GetInstance() { std::lock_guardstd::mutex lock(mtx); if (instance nullptr) { instance new Test(); return instance; } return instance; }; Test(const Test ) delete; Test operator(const Test ) delete; ~Test() { std::cout ~Test() std::endl; }; class Recycler { public: ~Recycler() { if (Test::instance) { delete Test::instance; } else { std::cout no need to recycle std::endl; } } }; static Recycler recycler; void Do() { std::cout Do() std::endl; } private: static Test *instance; static std::mutex mtx; Test() { std::cout Test() std::endl; }; }; Test *Test::instance nullptr; std::mutex Test::mtx; Test::Recycler recycler; void TestDo(Test test) { test.Do(); } int main() { Test *test Test::GetInstance(); test-Do(); return 0; }如下是一种很常用的单例模式。static Singleton instance() {std::call_once(flag, [] {instancePtr.reset(new Singleton());});return *instancePtr;}3 default这个关键字和 c 编译器默认生成的函数有关系。如果定义了一个空类那么 c 编译器会默认生成构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动拷贝构造函数、移动赋值运算符、取地址运算符、取值运算符符。毕竟这些运算符是一个对象最基础的运算符。以构造函数为例如果我们自己没有定义构造函数那么编译器会默认生成一个无形参的构造函数如果我们定义了有形参的构造函数那么编译器就不会生成默认无形参的构造函数了。而默认的构造函数在某些使用场景下也会用到这个时候我们就不需要自己定义一个这样的构造函数而是使用 default 关键字来提醒编译器虽然我自己写了有参数的构造函数但是也让编译器生成默认构造函数。写代码就是和编译器沟通。如下代码如果不将 Test声明为 default那么在 main() 函数中的 Test t1 这行代码便会导致编译错误。#include iostream #include string class Test { public: Test() default; Test(std::string str) { std::cout Test(), str str std::endl;; } }; int main() { Test t1; Test t2(hello); return 0; }自己定义的构造函数需要和 default 构造函数形成重载不能定义一个和 default 构造函数一样的构造函数这样是不允许的比如下边的代码。两个一样的函数怎么重载这样当然是不允许的。#include iostream #include string class Test { public: Test() default; Test() { std::cout Test() std::endl;; } }; int main() { Test t1; return 0; }4 override显式指定覆写。1override 可以显式的说明函数覆盖了基类中的虚函数增加了可读性。对于虚函数来说即使不使用 override 指定子类也可以覆写基类中的虚函数。2对于非虚函数来说派生类不能覆写父类的同名函数使用 override 编译时会报错。#include iostream #include string class Base { public: virtual void Do() { std::cout Base() Do()\n; } void Do1() { std::cout Base() Do1()\n; } }; class Derived : public Base { public: virtual void Do() override { std::cout Derived() Do()\n; } // 非虚函数不能使用 override // 派生类也无法覆写父类的同名函数 void Do1() /* override */ { std::cout Derived() Do1()\n; } }; int main() { Base *b new Derived; b-Do(); b-Do1(); return 0; }5 final1修饰类说明这个类不能被继承2非虚函数不能使用 final 来修饰。因为非虚函数本身就具有 final 的性质派生类不能覆写基类中的非虚函数3修饰虚函数派生类中不能覆写#include iostream #include string class Base { public: virtual void Do1() { std::cout Base() Do1()\n; } virtual void Do2() final { std::cout Base() Do2()\n; } }; class Derived1 final : public Base { public: virtual void Do1() override { std::cout Derived1() Do1()\n; } virtual void Do2() { std::cout Derived1() Do2()\n; } }; class Derived2 : public Derived1 { public: virtual void Do1() { std::cout Derived2() Do1()\n; } virtual void Do2() final { std::cout Derived2() Do2()\n; } }; int main() { Base *b1 new Derived1; Base *b2 new Derived2; b1-Do1(); b2-Do1(); return 0; }编译报错6 noexcept告诉编译器这个函数不会抛异常当这个函数出现异常的时候不会向上抛而是进程被 std::terminate 杀掉。1Do1使用 noexcept 修饰那么函数中抛异常的时候不会抛给上一级函数会直接被 std::terminate 杀掉2Do2使用 noexcept(true) 修饰作用和直接使用 noexcept 修饰相同函数内的异常不会抛给上一级函数会直接被 std::terminate 杀掉3Do3() 没有被 noexcept 修饰函数中抛出的异常可以抛给上一级函数4Do4() 被 noexcept(false) 修饰函数中抛出异常可以抛给上一级函数#include iostream #include string void Do1() noexcept { throw Do1 exception; } void Do2() noexcept(true) { throw Do2 exception; } void Do3() { throw Do3 exception; } void Do4() noexcept(false) { throw Do4 exception; } int main(int argc, char *argv[]) { if (argc ! 2) { std::cout argc is not 2\n; return 0; } printf(argv[1] %s\n, argv[1]); try { if (argv[1][0] 1) { std::cout call Do1()\n; Do1(); } else if (argv[1][0] 2) { std::cout call Do2()\n; Do2(); } else if (argv[1][0] 3) { std::cout call Do3()\n; Do3(); } else { std::cout call Do4()\n; Do4(); } } catch (const char *e) { std::cout exception: e std::endl; } return 0; }当你给函数加上noexcept相当于向编译器和调用者承诺“这个函数绝不会抛出异常”。移动语义的优化这是noexcept最著名的用途。当std::vector需要扩容时它会重新分配内存并移动旧元素。如果移动构造函数是noexcept的vector就会放心地使用“移动”来转移数据;如果不是为了保证异常安全(移动一般抛异常导致数据丢失)vector会退化为“拷贝”。生成更优地机器码编译器不需要为这种函数生成“栈回退”地代码因为不需要再抛出异常地时候销毁对象。者通常能让二进制文件更小运行速度微幅提升。