1 explicit
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 (C++11)
};
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;
}
2 delete
指定某个函数禁用。
在单例模式中,一个类只允许创建一个对象,同时也不允许这个类进行拷贝构造或者对象之间进行赋值。怎么做到不让类进行拷贝构造,以及不让对象之间进行赋值呢,这个时候就可以使用 delete 来修饰拷贝构造函数和赋值运算符。
如果在代码中使用了禁用的函数,那么编译的时候会报错。
#include <iostream>
#include <mutex>
class Test {
public:
static Test *GetInstance() {
std::lock_guard<std::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;
}
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
显式指定,覆写。
(1)override 可以显式的说明函数覆盖了基类中的虚函数,增加了可读性。对于虚函数来说,即使不使用 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 final
(1)修饰类,说明这个类不能被继承
(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 杀掉。
(1)Do1() 使用 noexcept 修饰,那么函数中抛异常的时候,不会抛给上一级函数,会直接被 std::terminate 杀掉
(2)Do2() 使用 noexcept(true) 修饰,作用和直接使用 noexcept 修饰相同,函数内的异常不会抛给上一级函数,会直接被 std::terminate 杀掉
(3)Do3() 没有被 noexcept 修饰,函数中抛出的异常,可以抛给上一级函数
(4)Do4() 被 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;
}