【问题标题】:Which C++ idioms are deprecated in C++11?在 C++11 中不推荐使用哪些 C++ 习语?
【发布时间】:2012-02-15 18:27:42
【问题描述】:

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

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

在回答这个问题时,您可以跳过“使用自动变量”之类的明显内容。

【问题讨论】:

  • 你不能弃用成语。
  • Herb Sutter's talk 在 Going Native 2012 上报道了这一点:
  • 不再鼓励返回常量值。显然auto_ptr 也已被弃用。
  • 当然可以,Pubby。在发明 C++ 模板之前,有一种宏技术来制作模板。然后 C++ 加了它们,老方法被认为不好。
  • 这个问题确实需要移到Programmers.se。

标签: c++ c++11 c++-faq


【解决方案1】:
  1. Final Class:C++11 提供了final 说明符来防止类派生
  2. C++11 lambda 大大减少了对命名函数对象(函子)类的需求。
  3. Move Constructor:由于对右值引用的一流支持,不再需要 std::auto_ptr 工作的神奇方式。
  4. Safe bool:这个前面提过。 C++11 的显式运算符消除了这个非常常见的 C++03 习语。
  5. Shrink-to-fit:许多 C++11 STL 容器提供了一个 shrink_to_fit() 成员函数,这应该消除了与临时交换的需要。
  6. Temporary Base Class:一些旧的 C++ 库使用这个相当复杂的习语。有了移动语义,就不再需要它了。
  7. Type Safe Enum 枚举在 C++11 中非常安全。
  8. Prohibiting heap allocation= delete 语法是一种更直接的方式来表示特定功能被明确拒绝。这适用于防止堆分配(即,=delete 用于成员 operator new)、防止复制、分配等。
  9. Templated typedef: C++11 中的Alias templates 减少了对简单模板化类型定义的需求。但是,复杂类型生成器仍然需要元函数。
  10. 可以使用generalized constant expressions 轻松替换一些数值编译时计算,例如斐波那契
  11. result_of:使用类模板result_of 应替换为decltype。我认为result_of 在可用时使用decltype
  12. In-class member initializers 保存输入以使用默认值对非静态成员进行默认初始化。
  13. 在新的 C++11 代码中,NULL 应重新定义为 nullptr,但请参阅 STL's talk 了解他们决定反对它的原因。
  14. Expression template 狂热者很高兴在 C++11 中拥有 trailing return type 函数语法。不再有 30 行长的返回类型!

我想我会停在那里!

【讨论】:

  • 感谢您提供的详细资料!
  • 很好的答案,但我会从列表中删除result_of。尽管之前需要麻烦的typename,但我认为typename result_of<F(Args...)::type 有时比decltype(std::declval<F>()(std::declval<Args>()...) 更容易阅读,并且在工作文件中接受N3436 后,他们都为SFINAE 工作(这曾经是一个优势decltype 中的 result_of 没有提供)
  • 关于 14) 我仍然在哭,我必须使用宏才能两次编写相同的代码——一次用于函数体,一次用于 decltype() 语句......跨度>
  • 我想指出,这个主题是从 this Microsoft page 链接到的,作为 C++ 语言一般介绍中的“更多信息”文章,但这个主题是一个高度专业化的主题!我可以建议在主题或此答案的开头包含一个简短的“此主题不适合 C++ 新手!” 建议吗?
  • Re 12:“类内成员初始化” - 这是新的成语,而不是过时的成语,不是吗?也许切换句子顺序? Re 2:当您想要传递类型而不是对象时(尤其是在模板参数中),函子非常有用。所以只有一些函子的使用被弃用了。
【解决方案2】:

曾有人争论说应该按const 值返回,而不仅仅是按值返回:

const A foo();
^^^^^

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

foo() = a;

但是通过 const 返回在 C++11 中是禁忌的,因为它禁止移动语义:

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

所以放松一下并编写代码:

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

【讨论】:

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

一旦您可以放弃0NULL 转而支持nullptr,就这样做!

在非泛型代码中,使用0NULL 并不是什么大问题。但是,一旦您开始在通用代码中传递空指针常量,情况就会迅速改变。当您将0 传递给template<class T> func(T) 时,T 会被推断为int 而不是空指针常量。之后它不能转换回空指针常量。如果宇宙只使用nullptr,这将导致一系列问题根本不存在。

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

【讨论】:

  • 什么是decltype(nullptr)?
  • @GrapschKnutsch:是std::nullptr_t
  • 建议将其改写为已弃用的成语,而不是采用新的约定(例如,“将0NULL 用于空指针”)。
【解决方案4】:

Safe bool idiomexplicit operator bool()

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

Simulating final class with private destructor and virtual inheritanceclass X final

【讨论】:

  • 好而简洁的例子,其中一个甚至带有“成语”这个词。说得好
  • 哇,我以前从未见过'安全布尔成语',看起来很恶心!我希望我在 C++11 之前的代码中永远不需要它...
【解决方案5】:

让您避免在 C++11 中编写基本算法的原因之一是 lambdas 与标准库提供的算法相结合的可用性。

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

一旦您使用带有完整 C++11 标准库的 C++11 编译器,您就再也没有理由不使用标准算法来构建您的编译器了。拉姆达干脆杀了它。

为什么?

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

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

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

【讨论】:

  • 其实有一个很好的借口。您正在使用 Boost.Range's 算法,这要好得多;)
  • 我没有看到带有 lambda 的 for_each 比等效的基于范围的 for 循环更好,其中 lambda 的内容在循环中。代码看起来或多或少相同,但 lambda 引入了一些额外的标点符号。您可以使用类似 boost::irange 的等价物将其应用于更多循环,而不仅仅是那些明显使用迭代器的循环。此外,基于范围的 for 循环具有更大的灵活性,如果需要,您可以提前退出(returnbreak),而使用for_each,您需要抛出。
  • @SteveJessop:即便如此,基于范围的for 的可用性使通常的it = c.begin(), const end = c.end(); it != end; ++it 成语失效。
  • @SteveJessop for_each 算法相对于基于范围的 for 循环的优势之一是您不能 breakreturn。也就是说,当您看到for_each 时,您无需看身体就知道没有这种狡猾。
  • @Klaim:具体来说,我将std::for_each(v.begin(), v.end(), [](int &i) { ++i; });for (auto &i : v) { ++i; } 进行比较。我接受灵活性是双刃的(goto 非常灵活,这就是问题所在)。我不认为在for_each 版本中不能使用break 的限制弥补了它所要求的额外冗长——for_each 的用户这里是 IMO 牺牲实际可读性和便利性来换取一种理论for_each原则上更清晰,概念上更简单。在实践中,它并没有更清晰或更简单。
【解决方案6】:

您需要减少实现swap 的自定义版本的频率。在 C++03 中,通常需要一个有效的非抛出 swap 以避免代价高昂和抛出副本,并且由于 std::swap 使用两个副本,因此 swap 通常必须自定义。在 C++ 中,std::swap 使用 move,因此重点转移到实现高效且不抛出的移动构造函数和移动赋值运算符上。因为对于这些,默认值通常就可以了,这将比在 C++03 中少得多。

通常很难预测会使用哪些成语,因为它们是通过经验创造出来的。我们可以期待“有效的 C++11”可能在明年发布,而“C++11 编码标准”仅需要三年时间,因为还没有必要的经验。

【讨论】:

  • 我对此表示怀疑。推荐的风格是使用 swap 进行移动和复制构造,而不是 std::swap 因为那将是循环的。
  • 是的,但是移动构造函数通常调用自定义交换,或者本质上是等效的。
【解决方案7】:

我不知道它的名字,但 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 结合而导致的任何复制。

【讨论】:

  • 在您的示例中,交换是不必要的,复制省略将在map 中构造返回值。如果map 已经存在,而不仅仅是正在构建,那么您展示的技术很有用。如果没有“便宜的默认构造函数”注释并且在该构造和交换之间使用“// ...”,该示例会更好
  • 我根据您的建议更改了它。谢谢。
  • “big”和“Bigger”的使用令人困惑。为什么不解释键的大小和值类型的重要性?
【解决方案8】:

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

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

对于所谓的包含运算符>>,我开始跳舞。在早期版本中,人们必须这样做

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

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

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

【讨论】:

  • 这个特性已经在之前的 C++ 中添加了。或者至少 Visual C++ 在多年前的标准讨论中实现了它。
  • @AlanBaljeu 当然,编译器/库中添加了许多非标准的东西。在 C++11 之前有大量的编译器具有“自动”变量声明,但是你不能确定你的代码实际上可以被其他任何东西编译。问题是关于标准,而不是关于“是否有任何编译器可以做到这一点”。
【解决方案9】:

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

【讨论】:

  • ...但是哪个成语已被弃用?
  • 不是成语,但它是一种不再需要的好习惯。即使编译器支持 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); }
  • 我暗示你应该将你的答案表述为“避免按价值返回的习惯不再相关,等等等等。”
猜你喜欢
  • 2012-03-17
  • 2010-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多