C ++ 11中弃用了哪些C ++习惯用法?


192

有了新标准,就有了新的做事方法,许多方法都比旧方法好,但是旧方法仍然可以。显然,出于向后兼容的原因,新标准并未正式弃用。因此,剩下的问题是:

哪种旧的编码方式绝对不如C ++ 11风格,我们现在可以做什么?

在回答这个问题时,您可以跳过一些明显的事情,例如“使用自动变量”。


13
您不能弃用成语。
Pubby 2012年


5
不再鼓励返回常量值。显然也auto_ptr被弃用了。
Kerrek SB 2012年

27
当然可以,Pubby。在C ++模板被发明之前,存在一种宏技术来做模板。然后C ++添加了它们,旧方法被认为是不好的。
艾伦·巴尔吉

7
这个问题确实需要移至Programmers.se。
Nicol Bolas 2012年

Answers:


173
  1. 最终类:C ++ 11提供了final防止类派生的说明符
  2. C ++ 11 lambda大大减少了对命名函数对象(functor)类的需求。
  3. 移动构造器std::auto_ptr由于对右值引用的一流支持,不再需要神奇的工作方式。
  4. 安全布尔:前面已经提到过。C ++ 11的显式运算符消除了这种非常常见的C ++ 03习惯用法。
  5. 缩小以适应:许多C ++ 11 STL容器提供shrink_to_fit()成员函数,这应该消除与临时交换的需要。
  6. 临时基类:一些旧的C ++库使用这种相当复杂的习惯用法。有了move语义,就不再需要它了。
  7. 类型安全枚举在C ++ 11中非常安全。
  8. 禁止堆分配= delete语法是一种明确表示拒绝特定功能的更为直接的方法。这适用于防止堆分配(即,=delete对于member operator new),防止复制,分配等。
  9. 模板化typedef:C ++ 11中的别名模板减少了对简单模板化typedef的需求。但是,复杂类型生成器仍然需要元函数。
  10. 某些数值编译时计算(例如斐波那契)可以使用广义常数表达式轻松替换
  11. result_of:类模板的使用result_of应替换为decltype。我想result_of使用decltype时,可用它。
  12. 类内成员初始化程序保存类型,以便使用默认值对非静态成员进行默认初始化。
  13. 在新的C ++ 11中,代码NULL应重新定义为nullptr,但是请参阅STL的演讲以了解为什么他们反对它。
  14. 表达式模板狂热者很高兴在C ++ 11中具有尾随返回类型函数语法。没有更多的30行长返回类型!

我想我会停在那里!


感谢您提供的详细资料!
艾伦·巴尔吉

7
很好的答案,但我会result_of从清单中删除。尽管typename之前需要进行繁琐的工作,但我认为typename result_of<F(Args...)::type有时它比阅读起来更容易decltype(std::declval<F>()(std::declval<Args>()...),并且随着N3436被工作文件接受,他们俩都为SFINAE工作(以前没有decltype这样result_of做的优点)
Jonathan Wakely

关于14)我仍在哭泣,我必须使用宏才能两次编写相同的代码-一次用于函数体,一次用于decltype()语句……

2
我想指出的是,此主题从Microsoft页作为“更多信息”文章链接到C ++语言的一般介绍中,但这是一个高度专门的主题!我可以建议一个简短的“本主题不适用于C ++新手!” 建议是否包含在主题的开头或此答案?
Aacini 2015年

关于12:“课堂内成员初始化”-这是新的习惯用法,不是弃用的习惯用法,不是吗?也许切换句子顺序?关于2:当您想传递类型而不是对象(尤其是模板参数)时,函子非常有用。因此,仅不推荐使用函子的某些用途。
einpoklum

66

在某个时间点,有人争论说,应该按const价值而不是仅仅按价值返回:

const A foo();
^^^^^

这在C ++ 98/03中几乎没有害处,甚至可能捕获了一些看起来像这样的错误:

foo() = a;

但是const在C ++ 11中禁止使用by返回,因为它禁止移动语义:

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

因此,放松并编写代码:

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

9
但是,现在可以通过使用函数的引用限定符来捕获可预防的错误。如上述情况中定义A& operator=(A o)&而不是A& operator=(A o)。这些可以防止愚蠢的错误,并使类的行为更像基本类型,并且不会阻止移动语义。

61

只要您可以放弃0NULL赞成,那就nullptr这样做吧!

在非泛型代码中,使用0NULL无关紧要。但是,一旦您开始在通用代码中传递空指针常量,情况就会迅速改变。当传递0给a时,template<class T> func(T) T将其推导为an int而不是空指针常量。此后不能将其转换回空指针常量。如果仅使用宇宙,这就变成了根本不存在的问题的泥潭nullptr

C ++ 11不会弃用0并且NULL为空指针常量。但是您应该像这样做一样进行编码。


什么是decltype(nullptr)?

4
@GrapschKnutsch:是std::nullptr_t
Howard Hinnant 2014年

建议将其改写为不赞成使用的成语,而不是采用要采用的新约定(例如“ 为空指针使用0NULL用于空指针”)。
einpoklum


24

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

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

一旦将C ++ 11编译器与完整的C ++ 11标准库一起使用,您就不再有理由不使用标准算法来构建自己的。Lambda只是杀死它。

为什么?

在实践中(在自己使用这种编写算法的方式之后),阅读用简单的单词(即已完成的工作)构建的内容要比使用某些必须解密才能知道其含义的循环要容易得多。就是说,自动推导lambda参数将大大有助于使语法更容易与原始循环进行比较。

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

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


8
其实有一个很好的借口。您正在使用Boost.Range的算法,效果更好;)
Nicol Bolas 2012年

10
我认为for_each使用lambda不会比等效的基于范围的for循环更好,因为lambda的内容在循环中。该代码看起来大致相同,但是lambda引入了一些额外的标点符号。您可以使用类似的功能,例如boost::irange将其应用于更多的循环,而不仅仅是显然使用迭代器的循环。另外,基于范围的for循环具有更大的灵活性,因为您可以根据需要(通过return或通过break)提早退出,而for_each需要抛出。
史蒂夫·杰索普

5
@SteveJessop:即便如此,基于范围的可用性for使通常的it = c.begin(), const end = c.end(); it != end; ++it习惯用法已失效。
Ben Voigt 2012年

7
@SteveJessop该for_each算法在基于for循环的范围内的优势之一是您不能 breakreturn。就是说,当您看到for_each而无需看身体便立即知道没有这种棘手的问题。
bames53 '02

5
@Klaim:具体而言,我比较例如std::for_each(v.begin(), v.end(), [](int &i) { ++i; });for (auto &i : v) { ++i; }。我接受灵活性具有双重优势(goto非常灵活,这就是问题所在)。我不认为不能够使用的限制breakfor_each版本补偿了它需要额外的冗长-用户for_each在这里被IMO牺牲可读性实际和方便的一种理论概念的for_each在原则更清晰,概念更简单。实际上,这并不清晰或更简单。
史蒂夫·杰索普

10

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

通常,很难预测将使用哪些惯用语,因为它们是根据经验创建的。我们可能会在明年看到“有效的C ++ 11”,而在三年之内只能得到“ C ++ 11编码标准”,因为还没有必要的经验。


1
我对此表示怀疑。推荐的样式是使用swap进行移动和复制构造,但不要使用std :: swap,因为那样会是循环的。
艾伦·巴尔吉

是的,但是move构造函数通常调用自定义交换,或者从本质上讲是等效的。
2012年

2

我不知道它的名字,但是C ++ 03代码经常使用以下构造代替丢失的移动分配:

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
在您的示例中,交换是不必要的,map无论如何复制省略都会构造返回值。您展示的技术如果map已经存在,将很有用,而不仅仅是被构造。这个例子会更好没有“廉价默认构造函数”的评论,并以“// ...”那个结构和交换之间
乔纳森Wakely

我已根据您的建议进行了更改。谢谢。
2014年

“大”和“大”的使用令人困惑。为什么不解释键的大小和值类型的关系呢?
einpoklum

1

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

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

因为据说包含运算符>>,我开始跳舞。在早期版本中,必须要做

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

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

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


1
此功能已在以前的C ++中添加。或者至少是Visual C ++早在很多年前就通过标准讨论实现了它。
艾伦·巴尔吉

1
@AlanBaljeu当然,编译器/库中添加了许多非标准的东西。在C ++ 11之前,有成千上万的编译器具有“自动”变量声明,但是您不确定自己的代码是否可以由其他任何东西编译。问题是关于标准,而不是关于“是否有任何编译器可以做到这一点”。
v010dya 2015年

1

按值返回不再是问题。使用移动语义和/或返回值优化(取决于编译器),编码功能更加自然,而没有开销或成本(大部分时间)。


...但是哪个惯用语已被弃用?
einpoklum

这不是习语,但是这是一种不再需要的好习惯。即使使用编译器支持的RVO,RVO也是可选的。 zh.wikipedia.org/wiki/Return_value_optimization “在C ++进化的早期,该语言无法有效地从函数返回类类型的对象被认为是一个弱点.....” struct Data {char bytes [ 16];}; void f(Data * p){//直接在* p中生成结果} int main(){Data d; f(&d); }
马丁

我在暗示您应该将答案表达为“避免按价值返回的习惯不再与等等等等有关”。
einpoklum
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.