I'm starting with this code:
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
...
}
I'd like to refactor this to pull out the shared "/tmp/tmpXXXXXX"
constant. Here is an attempt:
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
...
}
However, this doesn't compile. Changing tmpfile[]
to tmpfile[sizeof(kTmpfile)]
also doesn't work.
The below does work, but it uses a macro which is discouraged by my company's style guide (which is based on the Google Style Guide ).
#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
...
}
Is there any way to write this "nicely"? Without having to use a macro or hardcode the size? Or is the macro the best option for readability and maintainability?
Here are three approaches. Credit goes to @πάνταῥεῖ, @PSkocik, and @Asu for suggesting these, I just typed them up.
Approach 1a
constexpr auto kTmpfile = "/tmp/tmpXXXXXX";
void func1() {
std::string tmpfile = kTmpfile;
mkstemp(tmpfile.data());
...
}
Advantages:
Disadvantages:
std::string::data
returns a const char*
in C++14 and earlier (of course, you can use a const_cast
in C++14, but that's also bad) Approach 1b
constexpr auto kTmpfile = "/tmp/tmpXXXXXX";
void func1() {
std::string tmpfile = kTmpfile;
mkstemp(&tmpfile[0]);
...
}
Advantages:
Disadvantages:
Approach 2
constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";
void func1() {
char tmpfile[sizeof(kTmpfile)];
memcpy(tmpfile, kTmpfile, sizeof(kTmpfile));
mkstemp(tmpfile);
...
}
Advantages:
Disadvantages:
You could use std::array
and some template magic to determine the array size;
#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());
//...
}
To get to the data inside a std::array
the function data()
can be called.
As long as the char arrays are local, you can replace
char tmpfile[] = STR_LITERAL;
with
char tmpfile[sizeof kTmpfile]; memcpy(tmpfile,kTmpfile,sizeof tmpfile);
with theoretically no loss of efficiency.
Clang, for example, compiles both func1 and func2 in the snippet below into the same instructions on 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);
}
Assembly output:
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
This solution for refactoring the duplicate initialization string without using macros also works in plain C.
Using std::string
, while it would generally use the heap, which is quite a bit more expensive than the stack, shouldn't hurt here much either as you can expect the file creation to take at least a microsecond and thereby dominate over the heap allocation and string copy.
One more way, C++11 (or C++03 if you write a constructor):
struct KTmpfile { char value[...] = "/tmp/tmpXXXXXX"; };
void func1() {
KTmpfile tmpfile;
mkstemp(tmpfile.value);
...
}
Stack only, smallest code possible in the func*
users.
Note that you have to specify the size of value
, of course (or repeat the literal to find out the size).
In my view, this is the best way to do it, because what you really want is a type that you want to get several instances of -- you know its size and its initial value, so create a proper type for it.
And, at this point, since you have a type, you are one step into calling mkstemp
from the type itself, maybe even in the constructor, and then simply say:
void func1() {
KTmpfile tmpfile;
...
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.