手把手写C++服务器(13):C++11新特性之静态断言static_assert

Posted 沉迷单车的追风少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(13):C++11新特性之静态断言static_assert相关的知识,希望对你有一定的参考价值。

前言:我们学过很多断言,动态断言、零宽断言等等,这些在我之前的blog里都有总结。静态断言是C++11中的新技术,在服务端编程中经常使用,这里做一个学习总结。

目录

语法

优点

示例

参考

语法

static_assert(常量表达式,提示字符串)

如果第一个参数常量表达式的值为真(true或者非零值),那么static_assert不做任何事情,就像它不存在一样,否则会产生一条编译错误,错误位置就是该static_assert语句所在行,错误提示就是第二个参数提示字符串。

因为static_assert是编译过程中可计算的表达式,所以不能使用变量等,这样会造成编译报错。例如:

int func(int n) {
    static_assert(n < 0, "n is over 0");
    return 0;
}

因此,static_assert经常用于在代码的开头,预处理代码之后,用于检查一些编译支持错误、检查模板参数等。例如在编译前检查机器的位数是否是64位:

//该static_assert用来确保编译仅在32位的平台上进行,不支持64位的平台
//该语句可放在文件的开头处,这样可以尽早检查,以节省失败情况下耗费的编译时间
static_assert(sizeof(int) == 4, "64-bit code generation is not supported.");

优点

1、由于static_assert是编译期间断言,不生成目标代码,因此static_assert不会造成任何运行期性能损失

2、使用static_assert,可以在编译期发现更多的错误,用编译器来强制保证一些契约,帮助我们改善编译信息的可读性,尤其是用于模板时。

3、static_assert可以用在全局作用域中,命名空间中,类作用域中,函数作用域中,几乎可以不受限制的使用。

示例

在网络编程中,不同平台上消息类型长度可能不一样,而我们进行消息通信的时候,会检查消息长度(参考UDP/TCP保证可靠性问题),此时如果平台的标度不统一,就会造成验证消息长度失败,连接往往建立失败。因此在编译期间,检查消息类型的长度,如果失败直接退出,使用static_assert抛出异常。

这是一个roundtrip轮转的demo,使用UDP测量两个机器之间网络的延迟,static_assert在19行。具体的业务逻辑会在下一讲详细分析。

#include "InetAddress.h"
#include "Socket.h"

#include <thread>

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

const int g_port = 3123;

struct Message
{
  int64_t request;
  int64_t response;
} __attribute__ ((__packed__));

static_assert(sizeof(Message) == 16, "Message size should be 16 bytes");

int64_t now()
{
  struct timeval tv = { 0, 0 };
  gettimeofday(&tv, NULL);
  return tv.tv_sec * int64_t(1000000) + tv.tv_usec;
}

void runServer()
{
  Socket sock(Socket::createUDP());
  sock.bindOrDie(InetAddress(g_port));
  sock.bindOrDie(InetAddress(g_port));

  while (true)
  {
    Message message = { 0, 0 };

    struct sockaddr peerAddr;
    bzero(&peerAddr, sizeof peerAddr);
    socklen_t addrLen = sizeof peerAddr;
    ssize_t nr = ::recvfrom(sock.fd(), &message, sizeof message, 0, &peerAddr, &addrLen);
    if (nr == sizeof message)
    {
      message.response = now();
      ssize_t nw = ::sendto(sock.fd(), &message, sizeof message, 0, &peerAddr, addrLen);
      if (nw < 0)
      {
        perror("send Message");
      }
      else if (nw != sizeof message)
      {
        printf("sent message of %zd bytes, expect %zd bytes.\\n", nw, sizeof message);
      }
    }
    else if (nr < 0)
    {
      perror("recv Message");
    }
    else
    {
      printf("received message of %zd bytes, expect %zd bytes.\\n", nr, sizeof message);
    }
  }
}

void runClient(const char* server_hostname)
{
  Socket sock(Socket::createUDP());
  InetAddress serverAddr(g_port);
  if (!InetAddress::resolve(server_hostname, &serverAddr))
  {
    printf("Unable to resolve %s\\n", server_hostname);
    return;
  }

  if (sock.connect(serverAddr) != 0)
  {
    perror("connect to server");
    return;
  }

  std::thread thr([&sock] () {
    while (true)
    {
      Message message = { 0, 0 };
      message.request = now();
      int nw = sock.write(&message, sizeof message);
      if (nw < 0)
      {
        perror("send Message");
      }
      else if (nw != sizeof message)
      {
        printf("sent message of %d bytes, expect %zd bytes.\\n", nw, sizeof message);
      }

      ::usleep(200*1000);
    }
  });

  while (true)
  {
    Message message = { 0, 0 };
    int nr = sock.read(&message, sizeof message);
    if (nr == sizeof message)
    {
      int64_t back = now();
      int64_t mine = (back + message.request) / 2;
      printf("now %jd round trip %jd clock error %jd\\n",
             back, back - message.request, message.response - mine);
    }
    else if (nr < 0)
    {
      perror("send Message");
    }
    else
    {
      printf("received message of %d bytes, expect %zd bytes.\\n", nr, sizeof message);
    }
  }
}

int main(int argc, const char* argv[])
{
  if (argc < 2)
  {
    printf("Usage:\\nServer: %s -s\\nClient: %s server_hostname\\n", argv[0], argv[0]);
    return 0;
  }

  if (strcmp(argv[1], "-s") == 0)
  {
    runServer();
  }
  else
  {
    runClient(argv[1]);
  }
}

参考

以上是关于手把手写C++服务器(13):C++11新特性之静态断言static_assert的主要内容,如果未能解决你的问题,请参考以下文章

手把手写C++服务器(16):服务端多线程并发编程入门精讲

手把手写C++服务器:编译实操——打开gcc/g++世界

手把手写C++服务器:C++编译常见问题编译优化方法C++库发布方式

手把手写C++服务器:C++编译常见问题编译优化方法C++库发布方式

手把手写C++服务器(27):五大文件描述符零拷贝控制总结

手把手写C++服务器(25):万物皆可文件之socket fd