1.6 final/override/=default/=delete语法

finaloverride=default=deleteC++11添加的一组非常具有标记意义的新语法,我们来逐一介绍它们。

# 1.6.1 final关键字

final关键字修饰一个类,这个类将不允许被继承,这在其他语言(如Java)中早就实现了。在C++ 11final关键字要写在类名的后面,其他语言是写在class关键字的前面。示例如下:

class A final
{

};

class B : A
{

};
1
2
3
4
5
6
7
8
9

由于类A被声明成final,B继承A,编译器会报如下错误提示类A不能被继承:

error C3246: 'B' : cannot inherit from 'A' as it has been declared as 'final'
1

# 1.6.2 override关键字

C++语法规定,父类中加了virtual关键字的方法可以被子类重写,子类重写该方法时可以加或不加virtual关键字,例如像下面这样:

class A
{
protected:
    virtual void func(int a, int b)
    {
    }
};

class B : A
{
protected:
    virtual void func(int a, int b)
    {
    }
};

class C : B
{
protected:
    void func(int a, int b)
    {
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这种宽松的规定可能会带来两个问题:

  • 当我们阅读代码时,无论子类重写的方法是否添加了virtual关键字,我们都没法直观地确定该方法是否是重写的父类方法;
  • 如果我们在子类中不小心写错了需要重写的方法的函数签名(可能是参数类型、个数或返回值类型),这个方法就会变成一个独立的方法,这可能会违背我们最初想重写父类某个方法的初衷,而编译器在编译时并不会检查到这个错误。

为了解决以上两个问题, C++ 11引进了override关键字,其实override关键字并不是什么新语法,在Java等其他语言中早就支持。被override修饰的类方法是改写父类的同名方法,加了该关键字后在编译阶段,编译器会作相应的检查,如果其父类不存在相同签名格式的类方法,编译器会给出相应的错误提示。

情形一 父类没有子类标记了override的方法

class A
{

};

class B : A
{
protected:
    void func(int k, int d) override
    {
    }
};
1
2
3
4
5
6
7
8
9
10
11
12

由于父类A中没有func方法,编译器会提示错误:

error C3668: 'B::func' : method with override specifier 'override' did not override any base class methods
1

情形二 父类有子类标记了override的方法,但函数签名不一致

class A
{
protected:
    virtual int func(int k, int d)
    {
        return 0;
    }
};

class B : A
{
protected:
    virtual void func(int k, int d) override
    {
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

编译器会报同样的错误。

正确代码

class A
{
protected:
    virtual void func(int k, int d)
    {  
    }
};

class B : A
{
protected:
    virtual void func(int k, int d) override
    {
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 1.6.3 =default标记

如果一个C++类没有显式地给出构造函数、析构函数、拷贝构造函数、operator =这几类函数的实现,在需要它们时,编译器会自动生成;或者,在给出这些函数的声明时,如果没有给出其实现,编译器在链接时就会报错。如果使用**=default**标记这类函数,编译器会给出默认实现。我们来看一个例子:

class A
{

};

int main()
{
    A a;
    return 0;
}
1
2
3
4
5
6
7
8
9
10

这样的代码是可以编译通过的,因为编译器会默认生成一个A的无参构造函数,假设我们现在给A提供一个有参数形式的构造函数:

class A
{
public:
    A(int i)
    {
    }

};

int main()
{
    A a;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这个时候编译器就不会自动生成默认无参数的构造函数了,这段代码会编译出错,错误提示A没有合适的无参构造函数:

error C2512: 'A' : no appropriate default constructor available
1

我们这个时候可以手动给A加上无参构造函数,也可以使用**=default**语法强行让编译器自己生成:

class A
{
public:
    A() = default;

    A(int i)
    {
    }

};

int main()
{
    A a;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

=default笔者觉得最大的作用就是,在开发中简化了那些构造函数中没有实际的初始化代码的写法,尤其是声明和实现分别属于一个*.h和*.cpp文件。例如,对于类A,其头文件为a.h ,其实现文件为a.cpp,正常情况下我们需要在a.cpp文件中写其构造函数和析构函数的实现(可能没有实际构造和析构代码):

//a.h
class A
{
public:
    A();
    ~A();
};
1
2
3
4
5
6
7
//a.cpp
#include "a.h"

A::A()
{
}

A::~A()
{
}
1
2
3
4
5
6
7
8
9
10

a.cpp中构造函数和析构函数我们不得不写上,有了**=default关键字,我们可以在a.h**中直接写成:

//a.h
class A
{
public:
    A() = default;
    ~A() = default;
};
1
2
3
4
5
6
7
//a.cpp
#include "a.h"
//这里不用在写A的构造函数和析构函数的实现了
1
2
3

# 1.6.4 =delete标记

既然有强制让编译器生成构造函数、析构函数、拷贝构造函数、operator =的语法,那么也应该有禁止编译器生成这些函数的语法,没错,就是**=delete**。

在C++98/03规范中,如果我们想让一个类不能被拷贝(即不能调用其拷贝构造函数),可以将其拷贝构造和operator =函数定义成private的:

class A
{
public:
    A() = default;
    ~A() = default;

private:
    A(const A& a)
    {
    }

    A& operator =(const A& a)
    {
    }
};

int main()
{
    A a1;
    A a2(a1);
    A a3;
    a3 = a1;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

以上代码在利用a1构造a2时编译器会提示错误:

error C2248: 'A::A' : cannot access private member declared in class 'A'
error C2248: 'A::operator =' : cannot access private member declared in class 'A'
1
2

我们利用了这种方式间接实现了一个类不能被拷贝的功能,这也是继承自 boost::noncopyable 的类不能被拷贝的实现原理。现在有了 =delete 语法,我们直接使用该语法,直接禁止编译器生成这两个函数即可:

class A
{
public:
    A() = default;
    ~A() = default;

public:
    A(const A& a) = delete;

    A& operator =(const A& a) = delete;
};

int main()
{
    A a1;
    //A a2(a1);
    A a3;
    //a3 = a1;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

一般在一些工具类中,我们不需要用到构造函数、析构函数、拷贝构造函数、operator=这四个函数,为了防止编译器自己生成,同时也是为了减小生成的可执行文件的体积,笔者建议使用 =delete 语法将这四个函数“删除”,例如:

class EncodeUtil
{
public:
    static std::wstring EncodeUtil::AnsiToUnicode(const std::string& strAnsi);
    static std::string UnicodeToAnsi(const std::wstring& strUnicode);
    static std::string AnsiToUtf8(const std::string& strAnsi);
    static std::string Utf8ToAnsi(const std::string& strUtf8);
    static std::string UnicodeToUtf8(const std::wstring& strUnicode);
    static std::wstring Utf8ToUnicode(const std::string& strUtf8);

private:
    EncodeUtil() = delete;
    ~EncodeUtil() = delete;

    EncodeUtil(const EncodeUtil& rhs) = delete;
    EncodeUtil& operator=(const EncodeUtil& rhs) = delete;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上次更新: 2025/05/19, 16:52:22

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>