Jason Pan

升Modern C++遇到的问题及其解释

黄杰 / 2020-10-26


extern使用别的命名空间的变量

// declare
namespace other_name_space {
extern int variable_in_other_namespace;
}

// use
namespace local_name_space{
  int foo(){
    return other_name_space::variable_in_other_namespace;
  }
}

而不是:

extern int other_name_space::variable_in_other_namespace;

C++11正则表达式

C++中的标准<regex>使用的ECMAScript语法:

http://www.cplusplus.com/reference/regex/ECMAScript/

特别要记得:使用\\来转移字符串中的\

一个相对可用的URL解析正则

std::regex url_regex(
    "^(https?:\\/\\/)?"                    // protocol
    "(((\\w([\\w-]*\\w)*)\\.)+[a-z]{2,}|"  // domain name
    "((\\d{1,3}\\.){3}\\d{1,3}))"          // OR ip (v4) address
    "(\\:\\d+)?"                           // port
    "(\\/[-\\w%_.~+\\$\\(\\)]*)*"          // path
    "(\\?[;&\\w%_.~+=-]*)?"                // query string
    "(\\#[-\\w_]*)?$",                     // fragment locator
    std::regex_constants::icase);

C++11 符号前缀

GCC5开始使用两个双ABI,详细说明

In the GCC 5.1 release libstdc++ introduced a new library ABI that includes new implementations of std::string and std::list. These changes were necessary to conform to the 2011 C++ standard which forbids Copy-On-Write strings and requires lists to keep track of their size.

这种变化就是将std::list<int>定义为std::__cxx11::list<int>,但为了兼容性,库中有两种定义的实现:

In order to maintain backwards compatibility for existing code linked to libstdc++ the library’s soname has not changed and the old implementations are still supported in parallel with the new ones. This is achieved by defining the new implementations in an inline namespace so they have different names for linkage purposes, e.g. the new version of std::list is actually defined as std::__cxx11::list. Because the symbols for the new implementations have different names the definitions for both versions can be present in the same library.

但是如果我们使用已经编好的老库,而用新版本gcc去编使用这些老库的代码就回报错找不到符号:

undefined reference to `foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'

下边的例子可以呈现上边的错误:

// lib.h 
#include <string>
void foo(const std::string& s);
// lib.cc
#include "lib.h"
void foo(const std::string& s) {}
#include "lib.h"

int main() {
  foo(std::string());
  return 0;
}
g++ -D_GLIBCXX_USE_CXX11_ABI=0 -c lib.cc
g++ -D_GLIBCXX_USE_CXX11_ABI=1 t.cc lib.o

使用nm等命令也可以看到使用D_GLIBCXX_USE_CXX11_ABI=0/1产生符号的区别:

nm lib.0.o 
0000000000000000 T _Z3fooRKSs

nm lib.1.o 
0000000000000000 T _Z3fooRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

Github没有release下载tar.gz包

wget https://github.com/{user}/{repo}/archive/master.tar.gz

替换中间两个变量{user}{repo}即可。比如:

wget https://github.com/panzhongxian/simple-perf-tools/archive/master.tar.gz

Bazel 编译尽量包含符号定义的动态库

使用cc_binary

User-defined literals / 用户定义字面量

下边的代码使用-std=c++98编译没有问题

#include <cstdio>

int main() {
  printf("s"__FILE__);
  return 0;
}

使用-std=c++11编译会有warning:

warning: invalid suffix on literal; C++11 requires a space between literal and string macro [-Wliteral-suffix]

Allows integer, floating-point, character, and string literals to produce objects of user-defined type by defining a user-defined suffix. … 8) user-defined string literal, such as “abd”_L or u"xyz"_M

这里User-defined literals是对应于普通的String literal等,而String literal常见的有:

" (unescaped_character|escaped_character)* "
L" (unescaped_character|escaped_character)* "
U" (unescaped_character|escaped_character)* "
R"delimiter( raw_characters )delimiter"

简单点说,用户定义字面量就是使用一个函数将值进行处理后返回。

std::string operator"" _x2(const char* str, std::size_t) {
  return std::string{str} + str; 
} 
std::string s = "abc"_x2;

变量s使用_x2之后的赋值的值则是"abcabc"

这样我们就理解了为什么"s"__FILE__编译告警,因为会将这个表达式可能会被当成用户定义字面量。解决方法就是按照提示,中间添加空格即可

make_pair的定义变化

下边的代码使用-std=c++98可以编过,但是使用-std=c++11编译会报错

#include <utility>

int foo(int a, int b) {
  std::pair<int, int> p = std::make_pair<int, int>(a, b);
  return 0;
}

报错内容:

error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
std::pair<int, int> p = std::make_pair<int, int>(a, b)

因为make_pair的定义在两个标准中发生了变化。

// c++98
template< class T1, class T2 >
std::pair<T1,T2> make_pair( T1 t, T2 u );

// c++11
template< class T1, class T2 >
std::pair<V1,V2> make_pair( T1&& t, T2&& u );

修改方法任选一种:

  1. 使用右值引用的定义方式,在变量上加move:
std::pair<int, int> p = std::make_pair<int, int>(std::move(a), std::move(b));
  1. 直接使用pair的构造函数
std::pair<int, int> p = std::pair<int, int>(a, b);
  1. 如果真的是直接赋值给一个pair可以直接使用初始化列表
std::pair<int, int> p(a, b);
bazel-out/k8-fastbuild/bin/external/envoy/source/common/common/_virtual_includes/utility_lib/common/common/utility.h:567:37: error: 'make_unique' is not a member of 'std'
  567 |         current->entries_[c] = std::make_unique<TrieEntry<Value>>();
      |                                     ^~~~~~~~~~~
In file included from url_shortener/src/service.cpp:14:
url_shortener/src/message.h:14:7: note: 'itop::url_shortener::UrlShortenerMsg::UrlShortenerMsg()' is implicitly deleted because the default definition would be ill-formed:
   14 | class UrlShortenerMsg : public ItopBaseMsg {
      |       ^~~~~~~~~~~~~~~
url_shortener/src/message.h:14:7: error: uninitialized reference member in 'class itop::url_shortener::UrlShortenerMsg'
url_shortener/src/message.h:16:3: error: uninitialized reference member in 'class itop::common::ServerInfo&' [-fpermissive]
   16 |   UrlShortenerMsg(){};
      |   ^~~~~~~~~~~~~~~
url_shortener/src/message.h:52:29: note: 'itop::common::ServerInfo& itop::url_shortener::UrlShortenerMsg::cache_server_info_' should be initialized
   52 |   itop::common::ServerInfo& cache_server_info_;
      |                             ^~~~~~~~~~~~~~~~~~
char* format_time(time_t tm) {
  static char str_tm[1024];
  struct tm tmm;
  memset(&tmm, 0, sizeof(tmm));
  localtime_r((time_t*)&tm, &tmm);

  snprintf(str_tm, sizeof(str_tm), "%04d-%02d-%02d %02d:%02d:%02d",
           tmm.tm_year + 1900, tmm.tm_mon + 1, tmm.tm_mday, tmm.tm_hour,
           tmm.tm_min, tmm.tm_sec);
  return str_tm;
};

这种非线程安全的函数如何改造成线程安全的函数?