初始化数组时,我可以避免c++11移动吗
can I avoid a c++11 move when initializing an array?
我使用C++11功能来制作我自己的StrCat的较小实现,部分原因是为了尝试C++11可变模板。(同时,为了避免依赖一个新的库来获得我可以在几行代码中编写的东西,或者将较长的代码复制到我自己的程序中,添加关于其许可证的注释,等等)。
我的实现似乎可以工作,但如果没有StrCatPiece的move构造函数,我就无法做到这一点。这很麻烦,因为我看不出默认的移动构造函数是如何安全的:如果原始的StrCatPiece的piece_
引用了buf_
内的地址,那么新的StrCatPiece的piece_
也将引用原始的buf_
内的一个地址,而不是新的buf_
。我看不出任何保证原始缓冲区会一直存在,直到它不再被引用。
$ g++ --version
g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
无移动构造函数:
$ g++ -Wall -g --std=c++11 strcattest.cc -o strcattest
strcattest.cc: In instantiation of ‘std::__cxx11::string StrCat(Types ...) [with Types = {const char*, int}; std::__cxx11::string = std::__cxx11::basic_string<char>]’:
strcattest.cc:54:33: required from here
strcattest.cc:39:38: error: use of deleted function ‘StrCatPiece::StrCatPiece(const StrCatPiece&)’
auto pieces = {StrCatPiece(args)...};
...
我的编译器显然忽略了我的自定义移动构造函数
$ g++ -Wall -g --std=c++11 strcattest.cc -o strcattest
$ ./strcattest
foo 26
但是,如果我强迫它与调用abort()
的自定义移动构造函数一起运行,程序就会像预期的那样崩溃:
$ g++ -Wall -g -fno-elide-constructors --std=c++11 strcattest.cc -o strcattest
$ ./strcattest
Aborted
如果我有一个默认的move构造函数,它似乎可以工作,但我很怀疑。。。所以在gdb中,我可以确认StringPiece指向当前缓冲区之外的其他地方:
(gdb) break 39
Breakpoint 1 at 0x401249: file strcattest.cc, line 39.
(gdb) run
Starting program: /home/slamb/strcattest
Breakpoint 1, StrCat<char const*, int> () at strcattest.cc:39
39 size_t size = 0;
(gdb) print pieces
$1 = {_M_array = 0x7fffffffe9b0, _M_len = 2}
(gdb) print pieces._M_array
$2 = (std::initializer_list<StrCatPiece>::iterator) 0x7fffffffe9b0
(gdb) print pieces._M_array[1]
$3 = {piece_ = {ptr_ = 0x7fffffffe972 "26", length_ = 2,
static npos = <optimized out>},
buf_ = " 06 00 00 00 00 00 00 00360350G367377177 00 00 01 00 62 66"}
(gdb) print (void*)pieces._M_array[1].buf_
$4 = (void *) 0x7fffffffe9e8
(gdb) print (void*)pieces._M_array[1].buf_ + 20
$6 = (void *) 0x7fffffffe9fc
特别是,0x7fffffffe972不在[0x7ffffffffe9e8,0x7ff fffffe9fc)!中
我可以定义一个有效的复制构造函数,但我想知道是否有办法完全避免复制/移动。
这是代码:
// Compile with: g++ -Wall --std=c++11 strcattest.cc -o strcattest
#include <re2/stringpiece.h>
#include <stdlib.h>
#include <iostream>
class StrCatPiece {
public:
explicit StrCatPiece(uint64_t p);
explicit StrCatPiece(re2::StringPiece p) : piece_(p) {}
StrCatPiece(const StrCatPiece &) = delete;
//StrCatPiece(StrCatPiece &&) { abort(); }
StrCatPiece &operator=(const StrCatPiece &) = delete;
const char *data() const { return piece_.data(); }
size_t size() const { return piece_.size(); }
private:
re2::StringPiece piece_;
char buf_[20]; // length of maximum uint64 (no terminator needed).
};
StrCatPiece::StrCatPiece(uint64_t p) {
if (p == 0) {
piece_ = "0";
} else {
size_t i = sizeof(buf_);
while (p != 0) {
buf_[--i] = '0' + (p % 10);
p /= 10;
}
piece_.set(buf_ + i, sizeof(buf_) - i);
}
}
template <typename... Types>
std::string StrCat(Types... args) {
auto pieces = {StrCatPiece(args)...};
size_t size = 0;
for (const auto &p : pieces) {
size += p.size();
}
std::string out;
out.reserve(size);
for (const auto &p : pieces) {
out.append(p.data(), p.size());
}
return out;
}
int main(int argc, char** argv) {
std::cout << StrCat("foo ", 26) << std::endl;
return 0;
}
编辑:添加一个复制构造函数当然有效:
StrCatPiece::StrCatPiece(const StrCatPiece &o) {
const char* data = o.piece_.data();
if (o.buf_ <= data && data < o.buf_ + sizeof(o.buf_)) {
memcpy(buf_, data, o.piece_.size());
piece_.set(buf_, o.piece_.size());
} else {
piece_ = o.piece_;
}
}
仍然很好奇是否可以完全避免移动或复制StrCatPiece(始终,而不仅仅是作为编译器优化),或者如果不能,为什么不呢。
有一种相当简单的方法可以做到这一点。聚合初始化是一个复制初始化上下文,因此初始化数组元素而不产生概念复制的唯一方法是通过复制列表初始化,即从{braced-init-list}
初始化,但这不能使用显式构造函数,因此我们需要为StrCatPiece
提供一个非显式的构造函数,最好不要创建不需要的隐式转换。
因此,一个简单的包装类模板来包装实际的构造函数参数:
template<class U>
struct StrCatPieceArg {
explicit StrCatPieceArg(U u) : u(u) {}
U u;
};
以及StrCatPiece
的非explicit
构造函数,它接受StrCatPieceArg
,并将封装的参数转发给实际的构造函数。
template<class U>
StrCatPiece(StrCatPieceArg<U> arg) : StrCatPiece(arg.u) {}
这里并没有真正失去任何显式,因为获得StrCatPieceArg
的唯一方法是使用其显式构造函数。
这个想法的另一个变体是使用一个额外的标记参数而不是包装类模板来表示"是的,我真的想构造一个StrCatPiece
"。
我们现在可以制作一个StrCatPiece
的数组,注意复制列表初始化每个元素,这样就不会创建临时的,即使在概念上也是如此:
template <typename... Types>
std::string StrCat(Types... args) {
StrCatPiece pieces[] = {{StrCatPieceArg<Types>(args)}...};
// ^ ^
// These braces are important!!!
//...
}
- C 11移动无效
- C 11移动语义
- C 11移动构造函数未调用,默认构造函数首选
- 如何使用C 11移动语义以明确避免复制
- C++ 11 - 移动非局部变量是否安全
- c++11移动std::deque或std::list的插入
- 如何使用 C++11 移动语义从函数返回 std::vector
- C++11移动语义
- RVO和NRVO优化+C++11移动操作员
- C++11移动构造函数和赋值运算符
- 编辑 Google 的 C++ DISALLOW_COPY_AND_ASSIGN 预处理器宏以进行 C++11 移动语义
- DirectX 11 移动我的正方形
- C++11 移动构造函数的速度比 C++98 复制构造函数慢
- C++11 移动构造函数
- C++:在共享所有权的情况下,C++11移动语义可以避免指针吗
- 初始化数组时,我可以避免c++11移动吗
- 返回锁时C++11移动
- C++11:移动/复制构造不明确
- C++11移动语义是在做一些新的事情,还是只是让语义更清晰
- 需要一些帮助来理解c++ 11移动构造函数