C++字面值常量类

文章目录

  • C++字面值常量类
    • 1. 定义
    • 2. 原理
    • 3. 其他情况
    • 4. 总结

C++字面值常量类

在C++中,定义变量的时候可以指定常量属性,说明这个变量成为常量,无法直接改变;这个使用const限定符来限定,例如:

#include 
using namespace std;

int main(int args, char* argv[])
{
        const int a = args;
        return 0;
}

从上面的例子我们可以发现,虽然a是一个const,但是使用一个变量初始化的。

在C++中还有另外一种声明方式,叫做常量表达式(constexpr),那就需要赋值的表达式为编译期间的常量,例如:

#include 
using namespace std;

int main(int args, char* argv[])
{
        constexpr int b = args;
        return 0;
}

此时编译就会报错,如下:

constexpr.cpp: In function ‘int main(int, char**):
constexpr.cpp:6:20: error: ‘args’ is not a constant expression
  constexpr int b = args;
                    ^~~~

对于普通变量我们比较好理解,但是C++的类,也能声明成为字面值的常量类。

1. 定义

字面值的常量类有两种定义:

  1. 数据成员都是字面值类型(算术类型,引用和指针,以及字面值常量类)的聚合类是字面值常量类。
  2. 或者满足如下的定义:
    • 数据成员都必须是字面值类型(算术类型,引用和指针,以及字面值常量类)。
    • 类必须至少含有一个constexpr构造函数。
    • 如果一个数据成员含有类内初始值,则内置类型的初始值必须是一条常量表达式。或者如果成员属性某种类类型,则初始值必须使用成员自己的constexpr构造函数。
    • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

例如如下类:

#include 
using namespace std;

class CPoint
{
public:
        constexpr CPoint(int xx, int yy) : x(xx), y(yy){}
        void setx(int xx){x = xx;}
        void sety(int yy){y=yy;}
        constexpr int getx() const {return x;}
        constexpr int gety() const {return y;}
private:
        int x;
        int y;
};
int main(int args, char* argv[])
{
        constexpr CPoint point(100, 200);
        constexpr int data = point.getx() * point.gety();
        cout << data << endl;
        cout << point.getx() * point.gety() << endl;
        return 0;
}

2. 原理

这个就是一个字面值常量类,这个字面值常量类可以定义constexpr CPoint的对象,这种对象可以在编译的时候展开,但是展开也是有条件的,针对constexpr才会展开,不过应该每个编译器的实现各有差异,我们看一下上面这个的反汇编代码:

                constexpr CPoint point(100, 200);
   0x0000000008000919 <+31>:    movl   $0x64,-0x20(%rbp)
   0x0000000008000920 <+38>:    movl   $0xc8,-0x1c(%rbp)

19              constexpr int data = point.getx() * point.gety();
   0x0000000008000927 <+45>:    movl   $0x4e20,-0x24(%rbp)

20              cout << data << endl;
   0x000000000800092e <+52>:    mov    $0x4e20,%esi
   0x0000000008000933 <+57>:    lea    0x2006e6(%rip),%rdi        # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
   0x000000000800093a <+64>:    callq  0x80007d0 <_ZNSolsEi@plt>
   0x000000000800093f <+69>:    mov    %rax,%rdx
   0x0000000008000942 <+72>:    mov    0x200687(%rip),%rax        # 0x8200fd0
   0x0000000008000949 <+79>:    mov    %rax,%rsi
   0x000000000800094c <+82>:    mov    %rdx,%rdi
   0x000000000800094f <+85>:    callq  0x80007a0 <_ZNSolsEPFRSoS_E@plt>

21              cout << point.getx() * point.gety() << endl;
   0x0000000008000954 <+90>:    lea    -0x20(%rbp),%rax
   0x0000000008000958 <+94>:    mov    %rax,%rdi
   0x000000000800095b <+97>:    callq  0x8000a12 <CPoint::getx() const>
   0x0000000008000960 <+102>:   mov    %eax,%ebx
   0x0000000008000962 <+104>:   lea    -0x20(%rbp),%rax
   0x0000000008000966 <+108>:   mov    %rax,%rdi
   0x0000000008000969 <+111>:   callq  0x8000a22 <CPoint::gety() const>
   0x000000000800096e <+116>:   imul   %ebx,%eax
   0x0000000008000971 <+119>:   mov    %eax,%esi
   0x0000000008000973 <+121>:   lea    0x2006a6(%rip),%rdi        # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
   0x000000000800097a <+128>:   callq  0x80007d0 <_ZNSolsEi@plt>
   0x000000000800097f <+133>:   mov    %rax,%rdx
   0x0000000008000982 <+136>:   mov    0x200647(%rip),%rax        # 0x8200fd0
   0x0000000008000989 <+143>:   mov    %rax,%rsi
   
   0x000000000800098c <+146>:   mov    %rdx,%rdi
   0x000000000800098f <+149>:   callq  0x80007a0 <_ZNSolsEPFRSoS_E@plt>

对于constexpr int data = point.getx() * point.gety();这个,被编译器直接转换为了movl $0x4e20,-0x24(%rbp).

但是针对cout << point.getx() * point.gety() << endl;并没有进行编译器展开,反汇编为:

  1. callq 0x8000a12
  2. callq 0x8000a22
  3. imul %ebx,%eax

也就是说,字面值常量类只是对常量表达式计算进行了优化(但是这种讲法应该不是特别准确的,每个编译器实现上应该有所差异)。

3. 其他情况

int main(int args, char* argv[])
{
        CPoint point(100, 200);
        constexpr int data = point.getx() * point.gety();
        cout << data << endl;
        cout << point.getx() * point.gety() << endl;
        return 0;
}

如果去掉CPoint point的常量表达式属性,那么就不会编译器展开了,如下:

constexpr.cpp: In function ‘int main(int, char**):
constexpr.cpp:19:33:   in constexpr expansion of ‘point.CPoint::getx()constexpr.cpp:19:49: error: the value of ‘point’ is not usable in a constant expression
  constexpr int data = point.getx() * point.gety();
                                                 ^
constexpr.cpp:18:9: note: ‘point’ was not declared ‘constexpr’
  CPoint point(100, 200);

由于不是常量,编译期间展开,直接出错。

4. 总结

字面值常量类可以定义字面值常量的对象,例如:constexpr CPoint point(xxx).

用作常量表达式初始化的时候,编译期间自动展开constexpr int data = point.getx() * point.gety();.

你可能感兴趣的:(C++语言编程,c++)