哪些 C++ 习语在 C++11 中被弃用?

Which C++ idioms are deprecated in C++11?

提问人:Alan Baljeu 提问时间:2/16/2012 最后编辑:Toby SpeightAlan Baljeu 更新时间:1/14/2019 访问量:26231

问:

有了新的标准,就有了新的做事方式,很多都比旧方式好,但旧方式还是好的。同样明显的是,出于向后兼容性的原因,新标准并没有正式弃用太多。所以剩下的问题是:

哪些旧的编码方式肯定不如 C++11 风格,我们现在能做什么?

在回答这个问题时,你可以跳过“使用自动变量”等明显的事情。

C ++11 C ++-常见问题

评论

13赞 Pubby 2/16/2012
你不能弃用成语。
6赞 bames53 2/16/2012
Herb Sutter 在 Going Native 2012 上的演讲涵盖了以下内容:
5赞 Kerrek SB 2/16/2012
不再鼓励返回常量值。显然也被弃用了。auto_ptr
27赞 Alan Baljeu 2/16/2012
当然可以,Pubby。在 C++ 模板发明之前,有一种宏技术可以制作模板。然后 C++ 添加了它们,旧方法被认为是不好的。
7赞 Nicol Bolas 2/16/2012
这个问题确实需要移 Programmers.se。

答:

24赞 Klaim 2/16/2012 #1

让您避免用 C++11 编写基本算法的一件事是 lambda 与标准库提供的算法相结合的可用性。

我现在正在使用这些算法,令人难以置信的是,您经常通过使用 count_if()、for_each() 或其他算法来告诉您想做什么,而不必再次编写该死的循环。

一旦你使用带有完整 C++11 标准库的 C++11 编译器,你就没有充分的理由不使用标准算法来构建你的。Lambda 只是杀死它。

为什么?

在实践中(在我自己使用过这种编写算法的方式之后),阅读用直截了当的单词构建的东西感觉比阅读一些你必须解密才能知道含义的循环要容易得多。也就是说,自动推导 lambda 参数将有很大帮助,使语法更容易与原始循环进行比较。

基本上,使用标准算法制作的读取算法要容易得多,因为单词隐藏了循环的实现细节。

我猜现在只需要考虑更高级别的算法,因为我们有较低级别的算法可以构建。

评论

8赞 Nicol Bolas 2/16/2012
其实有一个很好的借口。您使用的是 Boost.Range 的算法,这些算法;)
10赞 Steve Jessop 2/16/2012
我不认为 lambda 比等效的基于范围的 for 循环更好,其中 lambda 的内容在循环中。代码看起来或多或少相同,但 lambda 引入了一些额外的标点符号。您可以使用等价物,例如将其应用于更多循环,而不仅仅是那些明显使用迭代器的循环。此外,基于范围的 for 循环具有更大的灵活性,因为您可以在需要时提前退出(by 或 by ),而 with 则需要抛出。for_eachboost::irangereturnbreakfor_each
5赞 Ben Voigt 2/16/2012
@SteveJessop:即便如此,基于范围的可用性使通常的成语不复存在。forit = c.begin(), const end = c.end(); it != end; ++it
7赞 bames53 2/16/2012
@SteveJessop 与基于范围的 for 循环相比,该算法的一个优点是你不能 。也就是说,当你看到时,你不看身体就立即知道没有这种棘手的事情。for_eachbreakreturnfor_each
5赞 Steve Jessop 2/16/2012
@Klaim:具体来说,我正在比较例如。我承认灵活性是一把双刃剑(非常灵活,这就是问题所在)。我不认为不能在版本中使用的约束可以弥补它需要的额外冗长——这里的用户是 IMO,为了一种理论概念而牺牲了实际的可读性和便利性,即原则上更清晰,概念上更简单。在实践中,它并不更清晰或更简单。std::for_each(v.begin(), v.end(), [](int &i) { ++i; });for (auto &i : v) { ++i; }gotobreakfor_eachfor_eachfor_each
66赞 Howard Hinnant 2/16/2012 #2

曾经有人认为,人们应该按价值返回,而不仅仅是按价值返回:const

const A foo();
^^^^^

这在 C++98/03 中基本上是无害的,甚至可能捕获了一些看起来像这样的错误:

foo() = a;

但是 C++11 中禁止返回 by,因为它抑制了移动语义:const

A a = foo();  // foo will copy into a instead of move into it

因此,只需放松并编码即可:

A foo();  // return by non-const value

评论

9赞 Joe 6/3/2013
但是,现在可以通过使用函数的引用限定符来捕获可预防的错误。如在上面的情况下定义而不是.这些可以防止愚蠢的错误,并使类的行为更像基本类型,并且不会阻止移动语义。A& operator=(A o)&A& operator=(A o)
10赞 Philipp 2/16/2012 #3

您需要较少地实现自定义版本。在 C++03 中,通常需要高效的非抛出以避免成本高昂的抛出副本,并且由于使用两个副本,因此通常必须自定义。在 C++ 中,使用 ,因此重点转移到实现高效且非抛出的移动构造函数和移动赋值运算符上。由于对于这些,默认值通常很好,因此这将比 C++03 中的工作量少得多。swapswapstd::swapswapstd::swapmove

一般来说,很难预测会使用哪些习语,因为它们是通过经验创造的。我们可以期待明年的“有效的C++11”,而“C++11编码标准”则要在三年内才能出现,因为必要的经验还没有。

评论

1赞 Alan Baljeu 2/18/2012
我对此表示怀疑。推荐的样式是使用 swap 进行移动和复制构造,但不要使用 std::swap,因为这将是循环的。
0赞 Inverse 2/20/2012
是的,但 Move 构造函数通常调用自定义交换,或者它本质上是等效的。
61赞 Howard Hinnant 2/16/2012 #4

只要你能放弃并赞成,就这样做!0NULLnullptr

在非泛型代码中,使用 or 并不是什么大问题。但是,一旦开始在泛型代码中传递 null 指针常量,情况就会迅速改变。当你传递给 a 时,会被推断为 a 而不是 null 指针常量。之后它不能转换回空指针常量。这陷入了问题的泥潭,如果宇宙只使用,这些问题根本不存在。0NULL0template<class T> func(T)Tintnullptr

C++11 不弃用 和 作为空指针常量。但是你应该像它一样编码。0NULL

评论

0赞 4/1/2014
什么是decltype(nullptr)?
5赞 Howard Hinnant 4/1/2014
@GrapschKnutsch:是的。std::nullptr_t
0赞 einpoklum 6/18/2016
建议将其改写为已弃用的习语,而不是要采用的新约定(例如,“空指针的使用或空指针的使用”)。0NULL
38赞 kennytm 2/16/2012 #5

安全的布尔成语→。explicit operator bool()

私有复制构造函数 (boost::noncopyable) →X(const X&) = delete

使用私有析构函数和虚拟继承→模拟最终类class X final

评论

0赞 Sebastian Mach 2/16/2012
好而简洁的例子,其中一个甚至带有“成语”这个词。说得好
2赞 boycy 2/23/2012
哇,我以前从未见过“安全布尔成语”,看起来很恶心!我希望我在 C++ 之前的代码中永远不需要它......
176赞 Sumant 2/18/2012 #6
  1. 最终类:C++11 提供了说明符以防止类派生final
  2. C++11 lambda 大大减少了对命名函数对象(函子)类的需求。
  3. 移动构造函数:由于对右值引用的一流支持,不再需要工作的神奇方式。std::auto_ptr
  4. Safe bool:前面已经提到过了。C++11 的显式运算符避免了这个非常常见的 C++03 习惯用语。
  5. 收缩适应:许多 C++11 STL 容器都提供了成员函数,这应该消除了与临时交换的需要。shrink_to_fit()
  6. 临时基类:一些旧的 C++ 库使用这个相当复杂的习惯用语。有了移动语义,就不再需要它了。
  7. 类型:安全枚举枚举在 C++11 中非常安全。
  8. 禁止堆分配:语法是一种更直接的说法,表示显式拒绝特定功能。这适用于防止堆分配(即,对于成员)、防止复制、分配等。= delete=deleteoperator new
  9. 模板化类型定义:C++11 中的别名模板减少了对简单模板化类型定义的需求。但是,复杂类型生成器仍然需要元函数。
  10. 一些数值编译时计算(如斐波那契)可以很容易地使用广义常量表达式进行替换
  11. result_of:类模板的用法应替换为 。我认为在可用时使用。result_ofdecltyperesult_ofdecltype
  12. 类内成员初始值设定项保存键入,以便使用默认值对非静态成员进行默认初始化。
  13. 在新的 C++ 中,代码应该重新定义为 ,但请参阅 STL 的演讲以了解他们为什么决定反对它。NULLnullptr
  14. 表达式模板狂热者很高兴在 C++11 中使用尾随返回类型函数语法。不再有 30 行长返回类型!

我想我会就此打住!

评论

7赞 Jonathan Wakely 1/18/2013
很好的答案,但我会从列表中删除。尽管之前需要繁琐,但我认为有时比 更容易阅读,并且随着 N3436 被接受到工作文件中,他们都为 SFINAE 工作(这曾经是它没有提供的优势)result_oftypenametypename result_of<F(Args...)::typedecltype(std::declval<F>()(std::declval<Args>()...)decltyperesult_of
0赞 4/1/2014
关于 14) 我仍然在哭泣,我必须使用宏才能编写两次相同的代码——一次用于函数体,一次用于 decltype() 语句......
2赞 Aacini 4/11/2015
我想指出的是,这个主题是从这个 Microsoft 页面链接的,作为 C++ 语言一般介绍中的“更多信息”文章,但这个主题是一个高度专业化的主题!我是否可以建议在主题或此答案的开头包含简短的“此主题不适合 C++ 新手!”建议?
0赞 einpoklum 6/18/2016
回复 12:“类内成员初始化”——这是新的成语,而不是弃用的成语,不是吗?也许可以改变句子顺序?Re 2:当您想要传递类型而不是对象时(尤其是在模板参数中),函子非常有用。因此,只有函子的一些用法被弃用。
0赞 einpoklum 1/14/2019
重新类内成员初始化:在 C++17 中,甚至可以初始化类定义中的静态成员。
2赞 Andrzej 10/11/2013 #7

我不知道它的名称,但 C++ 代码经常使用以下构造来替换缺少的移动赋值:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

这避免了由于复制省略与上述情况相结合而导致的任何复制。swap

评论

1赞 Jonathan Wakely 1/31/2014
在您的示例中,交换是不必要的,复制省略无论如何都会构造返回值。如果已经存在,您展示的技术是有用的,而不仅仅是被构建的。如果没有“廉价的默认构造函数”注释和“// ...”在该结构和交换之间mapmap
0赞 Andrzej 1/31/2014
我按照你的建议改变了它。谢谢。
0赞 einpoklum 6/18/2016
“big”和“Bigger”的使用令人困惑。为什么不解释一下键的大小和值类型的重要性呢?
2赞 v010dya 4/18/2015 #8

当我注意到使用 C++11 标准的编译器不再出现以下代码错误时:

std::vector<std::vector<int>> a;

据说包含操作员>>,我开始跳舞。在早期版本中,必须这样做

std::vector<std::vector<int> > a;

更糟糕的是,如果你不得不调试它,你就会知道由此产生的错误消息是多么可怕。

但是,我不知道这对你来说是否“显而易见”。

评论

1赞 Alan Baljeu 4/23/2015
此功能已在以前的 C++ 中添加。或者至少 Visual C++ 在很多年前就根据标准讨论实现了它。
1赞 v010dya 4/23/2015
@AlanBaljeu 当然,编译器/库中也添加了许多非标准的东西。在 C++11 之前,有大量的编译器具有“auto”变量声明,但是您无法确定您的代码实际上可以由其他任何东西编译。问题在于标准,而不是“是否有任何编译器可以做到这一点”。
1赞 Martin A 2/26/2016 #9

按值返回不再是问题。通过移动语义和/或返回值优化(依赖于编译器),编码函数更加自然,没有开销或成本(大多数时候)。

评论

0赞 einpoklum 6/18/2016
...但是哪个成语已被弃用?
0赞 Martin A 6/19/2016
这不是一个成语,但这是一个不再需要的好做法。即使使用编译器支持的 RVO,这是可选的。en.wikipedia.org/wiki/Return_value_optimization“在C++发展的早期阶段,该语言无法有效地从函数返回类类型的对象被认为是一个弱点......”struct Data { char bytes[16];void f(Data *p) { // 直接在 *p 中生成结果 } int main() { Data d; f(&d); }
0赞 einpoklum 6/20/2016
我暗示你应该把你的答案表述为“避免按价值回报的习惯不再相关,等等。