使用成员初始化程序时gcc中可能存在错误

Ard*_*kin 1 c++ g++ zeromq c++11

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

基本上,下面的代码摘录由g++and 解释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;
}
Run Code Online (Sandbox Code Playgroud)

当我编译上面的代码时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 
Run Code Online (Sandbox Code Playgroud)

使用相同的代码编译时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
Run Code Online (Sandbox Code Playgroud)

不过,我不希望看到最后一次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
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

当我编译代码时

~> clang++ -Wall -std=c++11 -O3 mwe.cpp -o mwe.out -lzmq
~> ./mwe.out
Run Code Online (Sandbox Code Playgroud)

我看到以下输出:

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
Run Code Online (Sandbox Code Playgroud)

但是,当我编译代码时

~> g++ -Wall -std=c++11 -O3 mwe.cpp -o mwe.out -lzmq
~> ./mwe.out
Run Code Online (Sandbox Code Playgroud)

我得到以下内容:

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)
Run Code Online (Sandbox Code Playgroud)

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

// message::message(std::vector<part> parts) noexcept : parts_{std::move(parts)} {}
message::message(std::vector<part> parts) noexcept {
  parts_ = std::move(parts);
}
Run Code Online (Sandbox Code Playgroud)

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

简而言之,我想知道我是否正在做一些可疑的事情导致g++编译代码不起作用,或者有可能g++存在一些错误.g++struct我使用的简单虚拟s 没有相同的行为(这就是为什么我不能用更简单的结构编写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;
}
Run Code Online (Sandbox Code Playgroud)

随着g++和原始zmq.hpp文件,我在GitHub的要点提供(当然,这个时候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
Run Code Online (Sandbox Code Playgroud)

但是,当我使用时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
Run Code Online (Sandbox Code Playgroud)

移动构造和移动分配似乎都适用于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));
}
Run Code Online (Sandbox Code Playgroud)

std::move

message::message(std::vector<part> parts) noexcept : parts_{std::move(parts)} {}
Run Code Online (Sandbox Code Playgroud)

出于这个原因,它创建了一个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));
}
Run Code Online (Sandbox Code Playgroud)

现在,两个g++clang++编译的二进制文件都可以正常工作.显然,模板化构造函数上的SFINAE禁用了initializer_list之前调用的构造函数调用,问题得到了解决.

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

T.C*_*.C. 8

铛实施DR 1467(括号初始化一个TT表现为如果你没有使用大括号),但仍未实施DR 2137(第二个想法,这样做只为聚集).

part可以从阳光下的所有东西中隐含地转换,包括std::vector<part>.由于std::vector<part>不是聚合,在DR2137之后,通常的两阶段重载解析发生parts_{std::move(parts)}并选择initializer_list<part>构造函数,转换std::move(parts)为a part.