Проблема упорядочения потоков, внутренней строки и операций с MSVC по сравнению с GCC / Clang
Всем привет,
Совсем недавно я начал более серьезно работать с MSVC для моего кроссплатформенного проекта, и при тестировании результатов через связанный поток STD (т.е. последовательность obj.foo() << endl << obj.bar() << endl << [..etc]
) я столкнулся с поведением при использовании внутренней обновленной строки, чего я не ожидал и не встречал в Linux с GCC или Clang.
Версии компилятора были GCC 7.5, Clang 11.0 и MSVC 14.0, все с включенным стандартом C ++ 17 (хотя и не завершенным). [изменить: та же проблема с MSVC 16.6.3 (внутренняя версия компилятора 19.26.28806.0)]
Для быстрого понимания вот упрощенная версия проблемы:
#include <iostream>
#include <ostream>
#include <string>
class Sample {
std::string s;
int x;
public:
Sample() = default;
friend std::ostream& operator<<(std::ostream& os, const Sample& a);
// Update internal value, return the object.
Sample const& set(std::string ss, int xx) {
s = ss;
x = xx;
return *this;
}
// Update internal value, return the string.
std::string const& setStr(std::string ss, int xx) {
set(ss, xx);
return s;
}
// Update internal value, return the int.
int const& setX(std::string ss, int xx) {
set(ss, xx);
return x;
}
};
// Output the object integer, same behavior with the string
// or if we flush inside or anything.
std::ostream& operator<<(std::ostream& os, Sample const& a)
{
os << a.x;
return os;
}
int main() {
Sample a;
// GCC / Clang | MSVC
std::cerr << a.set("0", 0) << std::endl // 0 0
<< a.set("1", 1) << std::endl // 1 0
<< a.set("2", 2) << std::endl; // 2 0
std::cerr << "content : " << a << std::endl; // 2 0
a.set("",-1); std::cerr << std::endl;
std::cerr << a.setStr("0", 0) << std::endl // 0 0
<< a.setStr("1", 1) << std::endl // 1 0
<< a.setStr("2", 2) << std::endl; // 2 0
std::cerr << "content : " << a << std::endl; // 2 0
a.set("",-1); std::cerr << std::endl;
std::cerr << a.setX("0", 0) << std::endl // 0 0
<< a.setX("1", 1) << std::endl // 1 1
<< a.setX("2", 2) << std::endl; // 2 2
std::cerr << "content : " << a << std::endl; // 2 2
}
Похоже, что со строкой или потоковой версией все операции используют один и тот же конечный измененный строковый объект, но я не могу понять, почему так (снова, без проблем с инструментами GNU / Linux).
Я мог бы добавить, что если мы отключим потоки, эта проблема с порядком исчезнет:
std::cerr << a.set("0", 0) << std::endl; // "0"
std::cerr << a.set("1", 1) << std::endl; // "1"
std::cerr << a.set("2", 2) << std::endl; // "2"
Сначала я подумал, что это проблема с промывкой, но тесты показали обратное. На самом деле использование endl
или даже flush
между каждым связанным вызовом ничего не дает.
Это может быть известное базовое поведение Visual-C ++ или даже CPP101 (в памяти и т. чертовски странно в моей книге.
Спасибо !
Изменить
Мне удалось воспроизвести проблему в GNU / Linux (с моим проектом, а не с приведенным выше кодом), иронично пытаясь найти альтернативу с помощью вариативного расширения шаблона, но вот вещи:
void println() // base function
{
std::cerr << std::endl;
}
template<typename T, typename... Ts>
constexpr void println(T head, Ts... tail)
{
std::cerr << head << std::endl;
println(tail...);
}
int main()
{
int i;
i = 0;
println(++i, ++i, ++i); // 3 3 3
i = 0;
println(i++, i++, i++); // 2 1 0
}
В MSVC поток, похоже, работает как этот вариативный шаблон пост-инкремента: результаты каким-то образом отстают (или больше похожи на рекурсивное применение пост-инкремента). Я не уверен, что это имело для меня смысл.
println(i++,i++,i++)
по-прежнему не имеет последовательности, даже в C ++ 17. Но в вашем первом случае это похоже на ошибку msvc. - person cigien   schedule 03.07.2020