初始化数组时可以避免c ++ 11移动吗?

时间:2015-12-06 00:54:05

标签: c++ c++11

我使用C ++ 11的功能来创建自己的StrCat的较小实现,部分是为了尝试C ++ 11可变参数模板。 (另外,为了避免因为我可以在几行代码中编写的内容或者将较长的代码复制到我自己的程序中,添加关于其许可证的说明等等而依赖于新库的烦恼。)

我的实现似乎有效,但如果没有StrCatPiece的移动构造函数,我无法做到这一点。这是令人不安的,因为我没有看到默认移动构造函数如何安全:如果原始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

如果我有一个默认的移动构造函数,它似乎工作,但我很怀疑...所以在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_ = "\006\000\000\000\000\000\000\000\360\350G\367\377\177\000\000\001\000\062\066"}
(gdb) print (void*)pieces._M_array[1].buf_
$4 = (void *) 0x7fffffffe9e8
(gdb) print (void*)pieces._M_array[1].buf_ + 20
$6 = (void *) 0x7fffffffe9fc

特别是,0x7fffffffe972不在[0x7fffffffe9e8,0x7fffffffe9fc中]!

我可以定义一个工作副本构造函数,但我想知道是否有办法完全避免复制/移动。

以下是代码:

// 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(总是,不仅仅是编译器优化),或者如果没有,为什么不呢。

1 个答案:

答案 0 :(得分:1)

这是一种相当骇人的方式。聚合初始化是一个复制初始化上下文,因此初始化数组元素而不产生概念副本的唯一方法是通过复制列表初始化,即从{braced-init-list}初始化,但这不能使用显式构造函数,所以我们需要给StrCatPiece一个非显式构造函数,最好不要创建不需要的隐式转换。

所以,一个简单的包装类模板来包装实际的构造函数参数:

template<class U>
struct StrCatPieceArg {
    explicit StrCatPieceArg(U u) : u(u) {}
    U u;
};

explicit的非StrCatPiece构造函数,它接受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!!!
    //...
}