构造函数新特性与新的关键字
- 构造函数新特性与新的关键字
- 委托构造与继承构造
- 委托构造
- 继承构造
- 其他关键字
- mutable
- const和mutable
- constexpr
- delete 和 default
- virtual、override 和 final
委托构造与继承构造
委托构造
在c++的类中有一个特性叫做函数重载——一个类中可以有多个相同名称的函数,只要他们的参数不一样即可,我们往往可以使用不同参数的函数去调用参数列表不一样但是函数名称相同的函数。同样的,在c++类中往往存在多个构造函数,并且他们可能有重复的参数,但是在c++11之前我们却无法在一个构造函数中调用该类中的其他构造函数。c++11中引入了委托构造的概念,你只需要将它看作是在构造函数中引入了普通重载函数相互调用的特性即可。简单使用如下:
class IpAddress
{
public:
IpAddress(const struct sockaddr_in &addr)
{
m_addr.sin_port = addr.sin_port;
m_addr.sin_family = addr.sin_family;
m_addr.addr.sin_addr = addr.addr.sin_addr;
}
IpAddress(const std::string &ip,const short port):IpAddress(IpAddress::from_ip_port(ip,port)){}
private:
static struct sockaddr_in from_ip_port(const std::string &ip,const short port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = ::htons(port);
::inet_pton(AF_INET, ip.c_str(), &addr.addr.sin_addr);
return addr;
}
private:
sockaddr_in m_addr;
}
上面的ipaddress类的第二个构造函数就是使用了委托构造,复用了第一个构造函数。
继承构造
先看下面的代码:
class Base
{
public:
Base(const int count):m_count(count);
virtual ~Base(){}
private:
int m_count;
};
class Child:public Base
{
public:
Child(const int count):Base(count){}
};
上面代码是在c++11之前的普遍的继承关系的代码实现。子类child需要提供一个与基类构造函数签名完全一致的一个构造函数来达到初始化基类的目的。在c++11中使用继承构造可以完全消除这部分重复的代码,如下:
class Child:public Base
{
public:
using Base::Base; // 声明Base的构造函数,让其在Child类中可见
};
Child c(10); // 使用继承构造初始化Child
其他关键字
mutable
mutable
在c++11中用在两方面:
- lambda表达式中,用来表示值捕获的变量是可以被修改的
- 和const一起使用,表示这个变量在逻辑上是const的
关于lambda表达式中使用mutable
表达式,可以看函数式编程——bind function 和 lambda 表达式,这里只说和const一起使用的情况。
const和mutable
const我们都很熟悉了,表示这个变量是不可以被修改的,除非使用const_cast。而mutable
则表示的是一种逻辑上的cosnt,比如一个对象中有某种状态,这个状态对外是不可见的但是在调用过程中却会发生改变。如果有一个const的对象要调用其成员函数,我们知道此时这个函数必须是cosnt的并且不能修改该对象的任何成员变量,这时候必须使用cosnt_cast达到我们的目的。在有了mutable之后,则不需要进行const_cast了,它告诉编译器,在const函数中修改该被mutable
修饰的变量是符合我们设计逻辑的。如下面这个例子:
这部分来自于 https://liam.page/2017/05/25/the-mutable-keyword-in-Cxx/
class HashTable {
public:
//...
std::string lookup(const std::string& key) const
{
if (key == last_key_) {
return last_value_;
}
std::string value{this->lookupInternal(key)};
last_key_ = key;
last_value_ = value;
return value;
}
private:
mutable std::string last_key_;
mutable std::string last_value_;
};
这里,我们呈现了一个哈希表的部分实现。显然,对哈希表的查询操作,在逻辑上不应该修改哈希表本身。因此,HashTable::lookup 是一个 const 成员函数。在 HashTable::lookup 中,我们使用了 last_key_
和last_value_
实现了一个简单的「缓存」逻辑。当传入的 key 与前次查询的 last_key_
一致时,直接返回 last_value_
;否则,则返回实际查询得到的 value 并更新 last_key_
和 last_value_
。
另外,要补充说明的是,mutable并不是一定要和const配合才能使用,比如下面代码的sub虽然不是const的,也可以修改mutable修饰的变量。
#include <iostream>
class m
{
public:
m():a(1){}
void add() const{
a++;
std::cout << "a=" << a << std::endl;
}
void sub(){
a--;
std::cout << "a=" << a << std::endl;
}
private:
mutable int a;
};
int main()
{
m x;
x.add();
x.sub();
return 0;
}
constexpr
constexp
是c++11中新引入的关键词,用来表示该表达式是一个常量表达式。编译器可以将常量表达式在编译期
计算完毕或者将结果插入到运行时代码中,减少运行时计算量,以提高程序运行效率。C++11 提供了constexpr
让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证该表达式在编译期就应该是一个常量表达式。
// 此处将进行函数调用
int max_name_length()
{
return 100;
}
// 此处将得到优化,减少函数调用
constexpr int const_max_name_length()
{
return 100;
}
constexpr int fibonacci(constint n){
return n ==1|| n ==2?1: fibonacci(n-1)+fibonacci(n-2);
}
{
const int len_1 = 10;
int arr_1[len_1]; // 合法
const int len_2 = len_1 +1;
constexpr int len_2_constexpr =1+2+3;
// char arr_4[len_2]; // 非法,C++ 标准中数组的长度必须是一个常量表达式,而对于 len_2 而言,这是一个 const 常数,而不是一个常量表达式
char arr_4[len_2_constexpr]; // 合法
// char arr_5[max_name_length()+5]; // 非法
char arr_6[const_max_name_length()+1]; // 合法
std::cout << fibonacci(10)<< std::endl; // 合法
// 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
std::cout << fibonacci(10)<< std::endl;
}
delete 和 default
我们知道当一个class有非静态成员变量,但是没有提供任何构造函数时,编译器将提供一个缺省的构造函数。当我们提供了除了拷贝构造以外的其他构造函数时,编译器将不再提供缺省的构造函数。但是有时候我们又希望该class可以进行缺省构造,在c++11之前我们需要自己显示的编写这部分代码,在c++11中只需要将缺省构造函数声明为default即可。同样的拷贝构造、拷贝赋值也可以被声明为default而不必提供任何实现。当一个构造函数或者赋值运算符是default的时候,将递归调用该class的其他成员变量进行赋值,因此在将构造函数或者赋值操作声明为default的时候,应该保证不会存在浅拷贝的问题。以下为default的简单使用例子:
class IpAddress
{
public:
IpAddress() = default;
IpAddress(const IpAddress &) = default;
IpAddress &operator=(const IpAddress &) = default;
private:
struct sockaddr_in m_addr;
};
virtual、override 和 final
- virtual:用于声明基类的成员函数为虚函数,可以被子类继承并且重写
- override:当一个成员函数被override修饰时,表示该函数继承自其父类,编译器将检查其父类中是否存在同样签名的虚函数,如果没有将报错。另外,override必须放在函数声明的最后~~
- final:一个类或者一个函数被声明为final的时候,表示其不可以再被继承或重写。