正在从常量初始化字符数组



我从以下代码开始:

void func1() {
char tmpfile[] = "/tmp/tmpXXXXXX";
mkstemp(tmpfile);  // Note: mkstemp modifies the char array, cannot be const
...
}
void func2() {
char tmpfile[] = "/tmp/tmpXXXXXX";
mkstemp(tmpfile);  // Note: mkstemp modifies the char array, cannot be const
...
}

我想对其进行重构,以提取共享的"/tmp/tmpXXXXXX"常量。这里有一个尝试:

constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";
void func1() {
char tmpfile[] = kTmpfile;
mkstemp(tmpfile);  // Note: mkstemp modifies the char array, cannot be const
...
}
void func2() {
char tmpfile[] = kTmpfile;
mkstemp(tmpfile);  // Note: mkstemp modifies the char array, cannot be const
...
}

但是,这不会编译。将tmpfile[]更改为tmpfile[sizeof(kTmpfile)]也不起作用。

下面的确实有效,但它使用了一个宏,这是我公司的风格指南(基于谷歌风格指南)所不鼓励的。

#define TMPFILE "/tmp/tmpXXXXXX"
void func1() {
char tmpfile[] = TMPFILE;
mkstemp(tmpfile);  // Note: mkstemp modifies the char array, cannot be const
...
}
void func2() {
char tmpfile[] = TMPFILE;
mkstemp(tmpfile);  // Note: mkstemp modifies the char array, cannot be const
...
}

有什么方法可以把这个写得"漂亮"吗?不必使用宏或硬编码大小?或者宏是可读性和可维护性的最佳选择?

这里有三种方法。信用为@πάγταῥεῖ, @PSkocik和@Asu提出了这些建议,我只是把它们打了出来。

接近1a

constexpr auto kTmpfile = "/tmp/tmpXXXXXX";
void func1() {
std::string tmpfile = kTmpfile;
mkstemp(tmpfile.data());
...
}

优点:

  • 代码更少/可读性更强

缺点:

  • 仅限C++17,因为std::string::data在C++14及更早版本中返回const char*(当然,您可以在C++14中使用const_cast,但这也很糟糕)
  • 可能较慢,因为char数组可能是在堆上而不是堆栈上分配的

方法1b

constexpr auto kTmpfile = "/tmp/tmpXXXXXX";
void func1() {
std::string tmpfile = kTmpfile;
mkstemp(&tmpfile[0]);
...
}

优点:

  • 代码更少/可读性更强
  • C++11及更高版本(请参阅"&s[0]"是否指向std::字符串中的连续字符?)

缺点:

  • 可能较慢,因为可以在堆上而不是堆栈上分配char数组

方法2

constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";
void func1() {
char tmpfile[sizeof(kTmpfile)];
memcpy(tmpfile, kTmpfile, sizeof(kTmpfile));
mkstemp(tmpfile);
...
}

优点:

  • 只使用堆栈,不使用堆
  • 与C++14及更早版本兼容

缺点:

  • 更详细/可读性更低

只要char数组是本地的,就可以替换

char tmpfile[] = STR_LITERAL; 

带有

char tmpfile[sizeof kTmpfile]; memcpy(tmpfile,kTmpfile,sizeof tmpfile); 

理论上没有效率损失。

例如,Clang将下面代码段中的func1和func2编译为x86_64:上的相同指令

#include <stdlib.h>
int func1() {
char tmpfile[] = "/tmp/tmpXXXXXX";
mkstemp(tmpfile);
}
#include <string.h>
const char kTmpfile[] = "/tmp/tmpXXXXXX";
int func2() {
char tmpfile[sizeof kTmpfile]; 
memcpy(&tmpfile,kTmpfile,sizeof tmpfile);
mkstemp(tmpfile);
}

装配输出:

func1(): # @func1()
subq $24, %rsp
movabsq $24866934413088880, %rax # imm = 0x58585858585870
movq %rax, 15(%rsp)
movabsq $8101259051807896623, %rax # imm = 0x706D742F706D742F
movq %rax, 8(%rsp)
leaq 8(%rsp), %rdi
callq mkstemp
func2(): # @func2()
subq $24, %rsp
movabsq $24866934413088880, %rax # imm = 0x58585858585870
movq %rax, 15(%rsp)
movabsq $8101259051807896623, %rax # imm = 0x706D742F706D742F
movq %rax, 8(%rsp)
leaq 8(%rsp), %rdi
callq mkstemp

这种在不使用宏的情况下重构重复初始化字符串的解决方案也适用于普通的C.

使用std::string,虽然它通常会使用堆,这比堆栈要贵得多,但在这里也不会有太大影响,因为您可以预期文件创建至少需要一微秒,从而控制堆分配和字符串复制。

您可以使用std::array和一些模板magic来确定数组大小;

#include <array>
#include <algorithm>
constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";
template<typename T, size_t N>
constexpr size_t array_size(T(&)[N])
{
return N;
}
void func1() {
std::array<char, array_size(kTmpfile)> var;
std::copy(std::begin(kTmpfile), std::end(kTmpfile), var.begin());
mkstemp(var.data());
//...
}

为了获取std::array内部的数据,可以调用函数data()

还有一种方法,C++11(或者C++03,如果您编写构造函数):

struct KTmpfile { char value[...] = "/tmp/tmpXXXXXX"; };
void func1() {
KTmpfile tmpfile;
mkstemp(tmpfile.value);
...
}

仅限堆栈,func*用户中可能的最小代码。

请注意,当然,您必须指定value的大小(或者重复文字来查找大小)。


在我看来,这是最好的方法,因为你真正想要的是一个你想获得几个实例的类型——你知道它的大小和初始值,所以为它创建一个合适的类型。

在这一点上,由于您有一个类型,您只需要从类型本身调用mkstemp一步,甚至可能在构造函数中,然后简单地说:

void func1() {
KTmpfile tmpfile;
...
}

最新更新