尽可能使用 auto

自动类型推导是现代C++中最重要且使用最广泛的功能之一。 新的C++标准使得可以在各种上下文中将auto用作类型的占位符,并让编译器推断出实际的类型。 在C++ 11中,auto可以用于声明局部变量和具有尾随返回类型的函数的返回类型。 在C++ 14中,auto可以用于函数的返回类型而无需指定尾随类型,并且可以用于lambda表达式中的参数声明。 未来的标准版本可能会将auto的使用范围扩大到更多情况。 在这些情况下使用auto有几个重要的好处。 开发人员应该意识到它们,并尽可能选择auto。 实际术语是由Andrei Alexandrescu创造的,并由Herb Sutter推广,即“几乎总是自动”(AAA)。

怎么做………

在以下情况下,请考虑将auto用作实际类型的占位符:

  • 当您不想提交给特定类型时,以auto name = expression形式声明局部变量:
auto i = 42;          // int 
auto d = 42.5;        // double 
auto s = "text";      // char const * 
auto v = { 1, 2, 3 }; // std::initializer_list<int> 
  • 当需要提交特定类型时,以 auto name = type-id {expression}形式声明局部变量:
auto b  = new char[10]{ 0 };            // char* 
auto s1 = std::string {"text"};         // std::string
auto v1 = std::vector<int> { 1, 2, 3 }; // std::vector<int>
auto p  = std::make_shared<int>(42);    // std::shared_ptr<int>
  • 以 auto name = lambda-expression的形式声明命名的lambda函数,除非需要传递lambda或返回到函数:
auto upper = [](char const c) {return toupper(c); };
  • 声明lambda参数并返回值:
auto add = [](auto const a, auto const b) {return a + b;};
  • 在不想提交特定类型时声明函数返回类型:
template <typename F, typename T>
auto apply(F&& f, T value)
{
    return f(value);
}

这个如何起作用……

auto 指示符基本上是实际类型的占位符。 使用auto时,编译器从以下实例推导实际类型:

  • 从用于初始化变量的表达式的类型开始,使用auto声明变量时。
  • 从尾随的返回类型或函数的返回表达式的类型开始,当auto用作函数的返回类型的占位符时。

在某些情况下,有必要提交给特定类型。 例如,在前面的示例中,编译器将s的类型推导为char const *。 如果要使用std::string,则必须明确指定类型。 类似地,v的类型推导为std::initializer_list 。 但是,其目的可能是拥有一个std::vector 。 在这种情况下,必须在赋值的右侧明确指定类型。

使用 auto 指示符而不是实际类型有一些重要的好处; 以下是一些最重要的几个:

  • 无法使变量保持未初始化状态。 这是开发人员在声明用于指定实际类型的变量时犯的一个常见错误,但是对于auto来说,编译器不可能通过初始化变量来推断类型。
  • 使用 auto 可确保您始终使用正确的类型,并且不会发生隐式转换。 考虑以下示例,在该示例中,我们检索向量的大小并传给局部变量。 在第一种情况下,变量的类型为int,尽管size()方法返回size_t。 这意味着将发生从size_t到int的隐式转换。 但是,对类型使用auto可以推断出正确的类型,即size_t:
auto v = std::vector<int>{ 1, 2, 3 };
int size1 = v.size();
// implicit conversion, possible loss of data
auto size2 = v.size();
auto size3 = int{ v.size() }; // error, narrowing conversion
  • 使用auto可以促进良好的面向对象的实践,例如,优先使用接口而不是具体实现。 指定的类型数量越少,代码就越通用,并且对将来的更改更开放,这是面向对象编程的基本原理。
  • 这意味着更少的输入,以及更少的关心我们不并不需要关心的实际类型。 即使我们明确指定了类型,但实际上我们并不在乎它。 迭代器是一种非常常见的情况,但是可以想到更多的情况。 当您要遍历一个范围时,您不必关心迭代器的实际类型。 您只对迭代器本身感兴趣; 因此,使用 auto 可以节省键入长名称的时间,并帮助您专注于实际代码而不是键入名称。 在以下示例中,在第一个for循环中,我们显式使用迭代器的类型。 要键入的文本很多,长语句实际上会使代码的可读性降低,并且您还需要知道实际上并不关心的类型名称。 auto 指示符的第二个循环看起来更简单,使您不必键入和关心实际类型。
std::map<int, std::string> m; 
for (std::map<int,std::string>::const_iterator it = m.cbegin(); it != m.cend(); ++it) 
{ /*...*/ } 
for (auto it = m.cbegin(); it != m.cend(); ++it)
{ /*...*/ }
  • 使用auto声明变量可提供一致的编码样式,其类型始终在右侧。 如果动态分配对象,则需要在赋值的左侧和右侧都写类型,例如,int p = new int(42)。 使用auto时,类型仅在右侧指定一次。

但是,使用auto时有一些陷阱:

  • auto 指示符仅是该类型的占位符,而不是const / volatile和引用说明符。 如果需要const / volatile和/或引用类型,则需要显式指定它们。 在下面的示例中,foo.get()返回对int的引用; 当从返回值初始化变量x时,编译器推导的类型为int,而不是int&。 因此,对x的任何更改都不会传播到foo.x_。 为此,应使用auto&:
class foo { 
  int x_; 
public: 
  foo(int const x = 0) :x_{ x } {} 
  int& get() { return x_; } 
}; 
foo f(42); 
auto x = f.get(); 
x = 100; 
std::cout << f.get() << std::endl; // prints 42
  • 不能将auto用于不可移动的类型:
auto ai = std::atomic<int>(42); // error
  • 对于多字类型(例如long long,long double或struct foo),不能使用auto。 但是,在第一种情况下,可能的解决方法是使用文字或类型别名。 与第二种情况一样,仅出于C兼容性在C ++中支持以这种形式使用struct / class,无论如何都应避免:
auto l1 = long long{ 42 }; // error 
auto l2 = llong{ 42 };     // OK 
auto l3 = 42LL;            // OK
  • 如果使用 auto 说明符,但仍需要知道类型,则可以在任何IDE中通过将光标置于变量上来做到这一点。 但是,如果您退出IDE,那将是不可能的了,知道实际类型的唯一方法是自己从初始化表达式中推断出它,这可能意味着在代码中搜索函数返回类型。

auto可用于指定函数的返回类型。 在C ++ 11中,这需要在函数声明中添加尾随返回类型。 在C ++ 14中,这已经放宽了,并且返回值的类型由编译器从return表达式推导出。 如果有多个返回值,则它们应具有相同的类型:

// C++11 
auto func1(int const i) -> int 
{ return 2*i; } 

// C++14 
auto func2(int const i) 
{ return 2*i; }

如前所述,auto不保留const / volatile和引用限定符。 这导致auto作为函数的返回类型的占位符出现问题。 为了解释这一点,让我们考虑使用foo.get()的前面的示例。 这次,我们有一个名为proxy_get()的包装函数,该函数接受一个foo的引用,调用get(),并返回get()返回的值int&。 但是,编译器将推断proxy_get()的返回类型为int,而不是int&。 尝试将该值分配给int&失败,并显示以下错误:

class foo 
{ 
  int x_; 
public: 
  foo(int const x = 0) :x_{ x } {} 
  int& get() { return x_; } 
}; 

auto proxy_get(foo& f) { return f.get(); } 

auto f = foo{ 42 }; 
auto& x = proxy_get(f); // 不能从 'int' 变为 'int &'

要解决此问题,我们需要实际返回auto&。 但是,这是模板的问题,可以完美地转发返回类型,而无需知道这是值还是引用。 C ++ 14中此问题的解决方案是decltype(auto),它将正确推断出类型:

decltype(auto) proxy_get(foo& f) { return f.get(); } 
auto f = foo{ 42 }; 
decltype(auto) x = proxy_get(f);

可以使用auto的最后一个重要情况是使用lambda。 从C ++ 14开始,lambda返回类型和lambda参数类型都可以是自动的。 这样的lambda称为通用lambda,因为由lambda定义的闭包类型具有模板化调用运算符。 下面显示了一个通用lambda,它带有两个自动参数,并返回对实际类型应用operator +的结果:

auto ladd = [] (auto const a, auto const b) { return a + b; }; 
struct 
{ 
  template<typename T, typename U> 
  auto operator () (T const a, U const b) const { return a+b; } 
} L;

此lambda可用于添加为其定义了operator +的任何内容。 在下面的示例中,我们使用lambda来添加两个整数并将其连接到std::string对象(使用C ++ 14用户定义的文字operator “”s):

auto i = ladd(40, 2);            // 42 
auto s = ladd("forty"s, "two"s); // "fortytwo"s

参见

  • 创建类型别名和别名模板
  • 了解统一初始化

最后更新于 2019-11-14 02:17 下午

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据