编辑。最后,我创建了关于下面原始问题的最小工作示例(感兴趣的读者可以阅读下面的长篇文章)。
基本上,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::part
为public
),我有以下内容:
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++
选择的正常移动操作。