使用成员初始化程序

时间:2018-04-13 13:09:30

标签: c++ c++11 g++ zeromq

编辑。最后,我创建了关于下面原始问题的最小工作示例(感兴趣的读者可以阅读下面的长篇文章)。

基本上,g++clang++对以下代码摘录的解释不同,这引起了头痛:

#include <iostream>
#include <vector>

struct part {
  part() = default;
  template <class T> part(const T &) {
    std::cout << __PRETTY_FUNCTION__ << '\n';
  }
};

struct message {
  message() = default;
  message(std::vector<part> parts) : parts_{std::move(parts)} {}

  std::size_t size() const noexcept { return parts_.size(); }

private:
  std::vector<part> parts_;
};

int main(int argc, char *argv[]) {
  part p1{5}, p2{6.8};

  message msg = {{p1, p2}};

  std::cout << "msg.size(): " << msg.size() << '\n';

  return 0;
}

当我使用clang++ -Wall -std=c++11 -O3 mwe.cpp -o mwe.out && ./mwe.out编译上述代码时,我得到以下内容:

part::part(const T &) [T = int]
part::part(const T &) [T = double]
msg.size(): 2 

使用g++编译相同的代码时,我得到以下内容:

part::part(const T&) [with T = int]
part::part(const T&) [with T = double]
part::part(const T&) [with T = std::vector<part>]
msg.size(): 1

我不希望看到最后一次part::part(const T&) [with T = std::vector<part>]电话。

我正在开发一个项目,我使用0MQ,因此使用zeromq标记。

我在代码中遇到了一个奇怪的问题,我不确定是g++中的错误还是我的包装0MQ库中的错误。我希望我能得到你的帮助。基本上,我正在测试

~> g++ --version
g++ (GCC) 7.3.1 20180312
Copyright (C) 2017 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.

~> clang++ --version
clang version 6.0.0 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

您可以在我的GitHub帐户中找到zmq.hpp文件,由于其长度,我不想在此处粘贴。我基于该标题的最小工作示例将显示为:

#include <iostream>
#include <string>

#include "zmq.hpp"

int main(int argc, char *argv[]) {
  auto version = zmq::version();
  std::cout << "0MQ version: v" << std::get<0>(version) << '.'
            << std::get<1>(version) << '.' << std::get<2>(version) << '\n';

  zmq::message msg1, msg2;
  std::string p1{"part 1"};
  uint16_t p2{5};
  msg1.addpart(std::begin(p1), std::end(p1));
  msg1.addpart(p2);

  std::cout << "msg1 is a " << msg1.numparts() << "-part message.\n";
  std::cout << "msg1[0]: " << static_cast<char *>(msg1.data(0)) << '\n';
  std::cout << "msg1[1]: " << *static_cast<uint16_t *>(msg1.data(1)) << '\n';

  msg2 = {{msg1[0], msg1[1]}};

  std::cout << "msg2 is a " << msg2.numparts() << "-part message.\n";
  std::cout << "msg2[0]: " << static_cast<char *>(msg2.data(0)) << '\n';
  std::cout << "msg2[1]: " << *static_cast<uint16_t *>(msg2.data(1)) << '\n';

  return 0;
}

当我用

编译代码时
~> clang++ -Wall -std=c++11 -O3 mwe.cpp -o mwe.out -lzmq
~> ./mwe.out

我看到以下输出:

0MQ version: v4.2.5
msg1 is a 2-part message.
msg1[0]: part 1
msg1[1]: 5
msg2 is a 2-part message.
msg2[0]: part 1
msg2[1]: 5

然而,当我用

编译代码时
~> g++ -Wall -std=c++11 -O3 mwe.cpp -o mwe.out -lzmq
~> ./mwe.out

我得到以下内容:

0MQ version: v4.2.5
msg1 is a 2-part message.
msg1[0]: part 1
msg1[1]: 5
msg2 is a 1-part message.
msg2[0]: <some garbage here>
fish: “./mwe.out” terminated by signal SIGSEGV (Address boundary error)

显然,由于我读了一个我不拥有的记忆位置,我得到了SIGSEGV。有趣的是,当我将zmq.hpp文件的Line 764更改为:

// message::message(std::vector<part> parts) noexcept : parts_{std::move(parts)} {}
message::message(std::vector<part> parts) noexcept {
  parts_ = std::move(parts);
}

在使用两个编译器编译时,代码按预期工作。

简而言之,我想知道我是否正在做一些可疑的事情导致g++编译的代码无效,或者g++有可能有一些错误。 g++与我使用的简单虚拟struct没有相同的行为(这就是为什么我不能用简单的结构编写MWE,这就是我怀疑的原因我的包装)。并且,-O0 -g开关也会出现相同的行为。

提前感谢您的时间。

编辑。我已将MWE更改为如下所示(根据@ Peter的评论):

#include <iostream>
#include <string>

#include "zmq.hpp"

int main(int argc, char *argv[]) {
  auto version = zmq::version();
  std::cout << "0MQ version: v" << std::get<0>(version) << '.'
            << std::get<1>(version) << '.' << std::get<2>(version) << '\n';

  zmq::message msg1, msg2;
  std::string data1{"part 1"};
  uint16_t data2{5};
  msg1.addpart(std::begin(data1), std::end(data1));
  msg1.addpart(data2);

  std::cout << "msg1 is a " << msg1.numparts() << "-part message.\n";
  // std::cout << "msg1[0]: " << static_cast<char *>(msg1.data(0)) << '\n';
  // std::cout << "msg1[1]: " << *static_cast<uint16_t *>(msg1.data(1)) << '\n';

  msg2 = {{msg1[0], msg1[1]}};

  std::cout << "msg2 is a " << msg2.numparts() << "-part message.\n";
  // std::cout << "msg2[0]: " << static_cast<char *>(msg2.data(0)) << '\n';
  // std::cout << "msg2[1]: " << *static_cast<uint16_t *>(msg2.data(1)) << '\n';

  zmq::message::part p1 = 5.0; // double
  std::cout << "[Before]: p1 has size " << p1.size() << '\n';
  zmq::message::part p2{std::move(p1)};
  std::cout << "[After]: p1 has size " << p1.size() << '\n';
  std::cout << "[After]: p2 has size " << p2.size() << '\n';

  zmq::message::part p3;
  std::cout << "[Before]: p3 has size " << p3.size() << '\n';
  p3 = std::move(p2);
  std::cout << "[After]: p2 has size " << p2.size() << '\n';
  std::cout << "[After]: p3 has size " << p3.size() << '\n';

  return 0;
}

使用g++和我在GitHub要点中提供的原始zmq.hpp文件(嗯,这次message::partpublic),我有以下内容:

0MQ version: v4.2.5
msg1 is a 2-part message.
msg2 is a 1-part message.
[Before]: p1 has size 8
[After]: p1 has size 0
[After]: p2 has size 8
[Before]: p3 has size 0
[After]: p2 has size 0
[After]: p3 has size 8

但是,当我使用clang++时,我会得到以下内容:

0MQ version: v4.2.5
msg1 is a 2-part message.
msg2 is a 2-part message.
[Before]: p1 has size 8
[After]: p1 has size 0
[After]: p2 has size 8
[Before]: p3 has size 0
[After]: p2 has size 0
[After]: p3 has size 8

移动构造和移动分配似乎都适用于message::part个对象。最后,valgrind ./mwe.out没有泄漏或错误。

编辑。我在周末调试了代码。似乎g++正在调用

template <class T> message::part::part(const T &value) : part(sizeof(T)) {
  std::memcpy(zmq_msg_data(&msg_), &value, sizeof(T));
}

之后的std::move之后的

message::message(std::vector<part> parts) noexcept : parts_{std::move(parts)} {}

出于这个原因,它会创建一个只有vector的{​​{1}},message::part({错误地)构造value = {msg1[0], msg1[1]}。但是,clang++执行正确的操作并且不调用模板化构造函数。

有没有办法解决这个问题?

编辑。我已相应修改了代码:

struct message {
private:
  struct part {
    /* ... */
    part(const T &,
         typename std::enable_if<std::is_arithmetic<T>::value>::type * =
             nullptr);
    /* ... */
  };
    /* ... */
};

template <class T>
message::part::part(
    const T &value,
    typename std::enable_if<std::is_arithmetic<T>::value>::type *)
    : part(sizeof(T)) {
  std::memcpy(zmq_msg_data(&msg_), &value, sizeof(T));
}

现在,g++clang++ - 编译的二进制文件都没有任何问题。显然,模板化构造函数上的SFINAE会禁用initializer_list上的构造函数调用,之前调用它,问题就解决了。

但是,我仍然想知道为什么g++更喜欢模板化构造函数调用clang++选择的正常移动操作。

1 个答案:

答案 0 :(得分:8)

Clang实施DR 1467(从T大括号初始化T的行为就像你没有使用大括号一样)但尚未实施DR 2137(在第二个想法,只为聚合做这个。)

您的part可以从阳光下的所有内容中隐式转换,包括std::vector<part>。由于std::vector<part>不是聚合,因此在DR2137之后,parts_{std::move(parts)}会进行通常的两阶段重载解析,并选择initializer_list<part>构造函数,将std::move(parts)转换为part