ein*_*ica 104 c++ language-lawyer standardization
(我正在寻找一两个例子来证明这一点,而不是一个列表。)
C++ 标准的变化(例如从 98 到 11、11 到 14 等)是否曾经改变了现有的、格式良好的、定义好的用户代码的行为——默默地?即在使用较新的标准版本进行编译时没有警告或错误?
笔记:
#if __cplusplus >= 201103L
.joh*_*ohn 112
C++ 17 中string::data
从const char*
to变化的返回类型char*
。这肯定会有所不同
void func(char* data)
{
cout << data << " is not const\n";
}
void func(const char* data)
{
cout << data << " is const\n";
}
int main()
{
string s = "xyz";
func(s.data());
}
Run Code Online (Sandbox Code Playgroud)
有点做作,但这个合法的程序会将其输出从 C++14 更改为 C++17。
cdh*_*wie 81
这个问题的答案显示了使用单个size_type
值初始化向量如何导致 C++03 和 C++11 之间的行为不同。
std::vector<Something> s(10);
Run Code Online (Sandbox Code Playgroud)
C++03 默认构造元素类型的临时对象,Something
并从该临时对象复制构造向量中的每个元素。
C++11 默认构造向量中的每个元素。
在许多(大多数?)情况下,这些会导致等效的最终状态,但没有理由必须这样做。这取决于Something
的默认/复制构造函数的实现。
看到这个人为的例子:
class Something {
private:
static int counter;
public:
Something() : v(counter++) {
std::cout << "default " << v << '\n';
}
Something(Something const & other) : v(counter++) {
std::cout << "copy " << other.v << " to " << v << '\n';
}
~Something() {
std::cout << "dtor " << v << '\n';
}
private:
int v;
};
int Something::counter = 0;
Run Code Online (Sandbox Code Playgroud)
C++03 将默认构造一个Something
,v == 0
然后从那个复制构造十个。最后,向量包含 10 个对象,其v
值为 1 到 10,包括 1 到 10。
C++11 将默认构造每个元素。不制作任何副本。最后,向量包含 10 个对象,其v
值为 0 到 9,包括 0 到 9。
cpp*_*ner 51
该标准在附件 C [diff] 中有一个重大更改列表。许多这些变化会导致沉默的行为改变。
一个例子:
int f(const char*); // #1
int f(bool); // #2
int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2
Run Code Online (Sandbox Code Playgroud)
Yak*_*ont 25
每次他们向标准库添加新方法(通常是函数)时,都会发生这种情况。
假设您有一个标准库类型:
struct example {
void do_stuff() const;
};
Run Code Online (Sandbox Code Playgroud)
很简单。在某些标准修订版中,添加了新方法或重载或任何内容:
struct example {
void do_stuff() const;
void method(); // a new method
};
Run Code Online (Sandbox Code Playgroud)
这可以默默地改变现有 C++ 程序的行为。
这是因为 C++ 目前有限的反射能力足以检测是否存在这样的方法,并基于它运行不同的代码。
template<class T, class=void>
struct detect_new_method : std::false_type {};
template<class T>
struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};
Run Code Online (Sandbox Code Playgroud)
这只是检测新的一种相对简单的方法method
,有无数种方法。
void task( std::false_type ) {
std::cout << "old code";
};
void task( std::true_type ) {
std::cout << "new code";
};
int main() {
task( detect_new_method<example>{} );
}
Run Code Online (Sandbox Code Playgroud)
从类中删除方法时也会发生同样的情况。
虽然这个例子直接检测到一个方法的存在,但这种事情是间接发生的可以不那么做作。作为一个具体的例子,您可能有一个序列化引擎,它根据某事物是否可迭代,或者是否具有指向原始字节的数据和一个大小成员来决定是否可以将其序列化为容器,其中一个优先于另一个。
该标准将一个.data()
方法添加到一个容器中,突然类型改变了它用于序列化的路径。
如果 C++ 标准不想冻结,那么它所能做的就是使这种默默中断的代码变得罕见或以某种方式不合理。
Noo*_*All 15
噢男孩...链接 cpplearner提供的是可怕的。
其中,C++20 不允许 C++ 结构体的 C 风格结构体声明。
typedef struct
{
void member_foo(); // Ill-formed since C++20
} m_struct;
Run Code Online (Sandbox Code Playgroud)
如果你被教导写这样的结构(而教“C with classes”的人正是这样教的),你就完蛋了。
Wax*_*rat 15
这是一个在 C++03 中打印 3 而在 C++11 中打印 0 的示例:
template<int I> struct X { static int const c = 2; };
template<> struct X<0> { typedef int c; };
template<class T> struct Y { static int const c = 3; };
static int const c = 4;
int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }
Run Code Online (Sandbox Code Playgroud)
这种行为变化是由对 的特殊处理引起的>>
。在 C++11 之前,>>
始终是右移运算符。使用 C++11,>>
也可以成为模板声明的一部分。
Adr*_*thy 11
源文件以物理字符集编码,该字符集以实现定义的方式映射到标准中定义的源字符集。为了适应某些物理字符集的映射,这些物理字符集本身并不具有源字符集所需的所有标点符号,该语言定义了三合字母——三个常见字符的序列,可以用来代替一个不太常见的标点符号。预处理器和编译器需要处理这些。
在 C++17 中,删除了三合字母。因此,某些源文件不会被较新的编译器接受,除非它们首先从物理字符集转换为其他一些物理字符集,该物理字符集一对一地映射到源字符集。(实际上,大多数编译器只是将三合字母的解释设为可选。)这不是一个微妙的行为变化,而是一个破坏性的变化,它阻止了以前可接受的源文件在没有外部翻译过程的情况下被编译。
char
该标准还提到了执行字符集,它是实现定义的,但必须至少包含整个源字符集加上少量的控制代码。
C++ 标准定义char
为可能无符号整数类型,可以有效地表示执行字符集中的每个值。根据语言律师的陈述,您可以争辩说 achar
必须至少为 8 位。
如果您的实现对 使用无符号值char
,那么您知道它的范围可以从 0 到 255,因此适合存储每个可能的字节值。
但是,如果您的实现使用有符号值,则它具有选项。
大多数会使用二进制补码,char
最小范围为 -128 到 127。这是 256 个唯一值。
但另一种选择是符号+幅度,其中一位保留用于指示数字是否为负,其他七位指示幅度。这将给出char
-127 到 127 的范围,即只有 255 个唯一值。(因为你失去了一个有用的位组合来表示 -0。)
我不知道该委员会曾明确指定这是一个缺陷,但它是因为你不能依赖标准从保证往返unsigned char
于char
和背部会保留原始值。(实际上,所有实现都这样做,因为它们都使用二进制补码来表示有符号整数类型。)
直到最近(C++17?)才修复了措辞以确保往返。该修复以及关于 的所有其他要求char
,有效地强制要求对有符号的补码进行补码,char
而无需明确说明(即使标准继续允许其他有符号整数类型的符号+大小表示)。有一个提议要求所有有符号整数类型都使用二进制补码,但我不记得它是否已进入 C++20。
所以这个与你正在寻找的有点相反,因为它为以前不正确的 过度自以为是的代码提供了追溯修复。
Adr*_*thy 10
我不确定您是否认为这是对正确代码的重大更改,但是......
在 C++11 之前,编译器被允许但不是必需的,在某些情况下省略副本,即使复制构造函数具有可观察到的副作用。现在我们保证了复制省略。行为本质上是从实现定义到必需的。
这意味着您的复制构造函数副作用可能在旧版本中发生,但在新版本中永远不会发生。您可能会争辩说正确的代码不应该依赖于实现定义的结果,但我认为这与说此类代码不正确完全相同。
从流中读取(数字)数据并且读取失败时的行为自 c++11 起已更改。
例如,从流中读取一个整数,而它不包含一个整数:
#include <iostream>
#include <sstream>
int main(int, char **)
{
int a = 12345;
std::string s = "abcd"; // not an integer, so will fail
std::stringstream ss(s);
ss >> a;
std::cout << "fail = " << ss.fail() << " a = " << a << std::endl; // since c++11: a == 0, before a still 12345
}
Run Code Online (Sandbox Code Playgroud)
由于 c++ 11 会在失败时将读取的整数设置为 0;在 c++ < 11 时,整数没有改变。也就是说,即使强制标准回到 c++98(使用 -std=c++98 ),gcc 也总是显示至少自 4.4.7 版以来的新行为。
(恕我直言,旧行为实际上更好:为什么在无法读取任何内容时将值更改为 0,这本身就是有效的?)
参考:见https://en.cppreference.com/w/cpp/locale/num_get/get
归档时间: |
|
查看次数: |
6002 次 |
最近记录: |