似乎auto
在C ++ 11中添加了相当重要的功能,该功能似乎遵循了许多较新的语言。与Python之类的语言一样,我没有看到任何显式的变量声明(我不确定是否可以使用Python标准)。
使用auto
声明变量而不是显式声明变量是否有缺点?
似乎auto
在C ++ 11中添加了相当重要的功能,该功能似乎遵循了许多较新的语言。与Python之类的语言一样,我没有看到任何显式的变量声明(我不确定是否可以使用Python标准)。
使用auto
声明变量而不是显式声明变量是否有缺点?
Answers:
您只问过缺点,所以我重点介绍其中一些。如果使用得当,auto
也具有几个优点。缺点来自易于滥用,以及代码以意想不到的方式行为的可能性增加。
主要缺点是,使用时auto
,您不一定知道要创建的对象的类型。在某些情况下,程序员可能希望编译器推断出一种类型,但是编译器坚决推断出另一种类型。
给像这样的声明
auto result = CallSomeFunction(x,y,z);
您不一定了解什么是类型result
。可能是一个int
。它可能是一个指针。可能还有别的东西。所有这些都支持不同的操作。您还可以通过较小的更改来显着更改代码,例如
auto result = CallSomeFunction(a,y,z);
因为根据CallSomeFunction()
结果的不同,结果的类型可能完全不同,因此后续代码的行为可能与预期的完全不同。您可能会在以后的代码中突然触发错误消息(例如,随后尝试取消引用an int
,尝试更改now const
)。更危险的更改是您的更改会越过编译器,但是后续代码的行为却不同且未知(可能有错误)。
因此,如果没有明确了解某些变量的类型,就很难严格地证明代码按预期工作。这意味着需要更多的努力来证明在高关键性(例如安全性或任务关键性)域中“适合目标”的主张是正确的。
另一个较常见的缺点是,诱惑程序员使用auto
钝器来强迫代码进行编译,而不是思考代码的作用并努力使其正确。
auto
,那么大多数鸭子式语言在设计上都会遭受此类缺点!
CallSomeFunction()
根据其参数的顺序返回不同的类型,则这是的设计缺陷CallSomeFunction()
,而不是的问题auto
。如果您在使用某个函数之前没有阅读该函数的文档,那是程序员的缺陷,而不是的问题auto
。-但我知道您在这里扮演恶魔的拥护者,只是Nir Friedman有更好的案例。
T CallSomeFunction(T, int, int)
设计缺陷?显然,它“根据其参数顺序返回不同的类型”。
auto
,您不一定知道所创建对象的类型。” 您能否详细说明为什么这是问题auto
,而不是子表达式临时对象的问题?为什么auto result = foo();
不好,但foo().bar()
不是呢?
auto
从原则上讲,这并不是一个缺点,但实际上对某些人来说,这是一个问题。基本上,有些人要么:a)auto
视作类型的救世主,在使用它时就闭上了大脑,或者b)忘记了auto
总是推论为价值的类型。这导致人们执行以下操作:
auto x = my_obj.method_that_returns_reference();
糟糕,我们只是复制了一些对象。通常是错误或性能失败。然后,您也可以用其他方式摆动:
const auto& stuff = *func_that_returns_unique_ptr();
现在,您会得到一个悬而未决的参考。这些问题根本不是由auto
所有问题引起的,因此我不认为它们是反对该问题的合理论据。但是auto
,由于我在开头列出的原因,这似乎使这些问题更普遍(根据我的个人经验)。
我认为,随着时间的流逝,人们会调整并理解分工:auto
推论出底层的类型,但是您仍然想考虑参考性和常量性。但这需要一些时间。
std::vector
)。复制昂贵不是类的属性,而是单个对象的属性。因此,method_that_returns_reference
可能会引用具有复制构造函数的类的对象,但是复制该对象的成本很高(并且无法从中移出)。
std::vector
?(因为可能,是的,或者是因为您不控制类,但这不是重点),如果复制昂贵(并且不拥有资源,因为它是可复制的),为什么不对对象使用COW?数据局部性已被对象的大小杀死。
= delete
过载。虽然更一般地说,您说的是解决方案。如果您感兴趣,这是我探讨的主题:nirfriedman.com/2016/01/18/…。
其他答案则提到了诸如“您真的不知道变量的类型是什么”之类的缺点。我想说这很大程度上与代码中的草率命名约定有关。如果您的接口名称明确,则无需关心确切的类型。当然,auto result = callSomeFunction(a, b);
不会告诉您太多。但是auto valid = isValid(xmlFile, schema);
告诉您足够使用,valid
而不必关心其确切类型是什么。毕竟,只有just if (callSomeFunction(a, b))
,您也不知道类型。与任何其他子表达式临时对象相同。因此,我认为这不是的真正缺点auto
。
我要说的主要缺点是,有时候,确切的返回类型不是您想要使用的类型。实际上,有时实际返回类型在实现/优化方面与“逻辑”返回类型有所不同。表达式模板是一个很好的例子。假设我们有这个:
SomeType operator* (const Matrix &lhs, const Vector &rhs);
从逻辑上讲,我们希望SomeType
是Vector
,并且我们绝对希望在代码中将其视为。但是,出于优化目的,我们使用的代数库有可能实现表达式模板,而实际的返回类型是:
MultExpression<Matrix, Vector> operator* (const Matrix &lhs, const Vector &rhs);
现在的问题是,MultExpression<Matrix, Vector>
很可能将a const Matrix&
和const Vector&
内部存储;它期望它将Vector
在完整表达式结束之前转换为a 。如果我们有此代码,一切都很好:
extern Matrix a, b, c;
extern Vector v;
void compute()
{
Vector res = a * (b * (c * v));
// do something with res
}
但是,如果我们在auto
这里使用过,可能会遇到麻烦:
void compute()
{
auto res = a * (b * (c * v));
// Oops! Now `res` is referring to temporaries (such as (c * v)) which no longer exist
}
auto
几乎没有缺点,所以我坚持这种实力。代理等的其他示例包括各种“字符串生成器”和在DSL中发现的类似对象。
auto
以前,我一直被表达式模板所困扰,尤其是对于Eigen库。这特别棘手,因为问题通常不会在调试版本中出现。
auto
涉及某种类型检查(并且auto
随处飞溅都会使该类型安全性消失,就像在其他地方一样)。这不是一个很好的比较。
唯一的缺点是,有时你不能声明const_iterator
与auto
。在这个问题的代码示例中,您将获得普通的(非const)迭代器:
map<string,int> usa;
//...init usa
auto city_it = usa.find("New York");
iterator
无论如何您都会得到一个,因为您的地图不是const
。如果要将其转换为const_iterator
,则可以照常显式指定变量类型,或者提取一个方法,以使map在您的上下文中为const find
。(我希望后者是SRP。)
auto city_it = static_cast<const auto&>(map).find("New York")
?或者,对于C ++ 17 ,auto city_if = std::as_const(map).find("New York")
。
它使您的代码更难阅读或繁琐。想象这样的事情:
auto output = doSomethingWithData(variables);
现在,要弄清楚输出的类型,您必须跟踪doSomethingWithData
功能的签名。
auto it = vec.begin();
比起阅读起来容易得多std::vector<std::wstring>::iterator it = vec.begin();
。
像这个开发人员一样,我讨厌auto
。或更确切地说,我讨厌人们如何滥用auto
。
我(强烈)认为这auto
是为了帮助您编写通用代码,而不是减少输入。
C ++是一种语言,其目标是让您编写健壮的代码,而不是减少开发时间。
从C ++的许多功能中可以很明显地auto
看出这一点,但是不幸的是,一些新的功能(例如减少打字)会误导人们以为他们应该开始对打字变得懒惰。
在早期auto
,人们使用typedef
s,这很棒,因为它typedef
允许库的设计者帮助您弄清楚返回类型应该是什么,以便他们的库按预期工作。当使用时auto
,您从类的设计者那里取消了该控件,而是要求编译器确定类型是什么,这从工具箱中删除了功能最强大的C ++工具之一,并有可能破坏其代码。
通常,如果使用auto
,那应该是因为您的代码适用于任何合理的类型,而不是因为您懒得写下它应该使用的类型。如果您auto
用作帮助懒惰的工具,那么将会发生的事情是您最终开始在程序中引入一些细微的错误,通常是由于您使用而不发生隐式转换而导致的auto
。
不幸的是,这些错误很难在此处的简短示例中进行说明,因为它们的简洁性使其比用户项目中的实际示例更具说服力;但是,它们很容易在大量模板代码中发生,这些代码期望某些隐式转换能够进行地点。
如果你想要一个例子,有一个在这里。不过,需要注意一点:在尝试跳转并批评代码之前:请记住,围绕此类隐式转换已开发了许多知名且成熟的库,它们之所以存在,是因为它们解决了即使不是不可能的困难的问题。否则解决。批评之前尝试找出更好的解决方案。
which was great because typedef allowed the designer of the library to help you figure out what the return type should be, so that their library works as expected. When you use auto, you take away that control from the class's designer and instead ask the compiler to figure out what the type should be
IMO并不是一个很好的理由。最新的IDE(例如Visual Studio 2015)允许您将鼠标悬停在上来检查变量的类型auto
。这是* *完全相同一样的typedef
一个。
typename std::iterator_traits<It>::value_type
。(2)整个点是,推断出类型需要不是“完全相同”作为正确的类型意在通过代码的先前设计师; 通过使用auto
,您将失去设计师指定正确类型的能力。
vector<bool>
废话” ……赦免?您如何bitset
实施?还是您认为位容器完全是胡说八道?
auto
没有缺点本身,我提倡(手波浪)在新代码中任何地方使用它。它使您的代码能够始终进行类型检查,并始终避免进行静默切片。(如果B
从派生A
而返回的函数A
突然返回B
,则auto
表现为预期的存储其返回值)
虽然,C ++ 11之前的旧版代码可能依赖于使用显式类型的变量引起的隐式转换。将显式类型的变量auto
更改为可能会改变代码的行为,因此您最好保持谨慎。
bool
而不是auto
更改任何内容吗?我可能是错的(无法在此处检查),但我认为唯一的区别是要bool
在变量声明而不是在条件中求值时进行转换if
。如果enum
作用域是范围,则在bool
没有明确通知的情况下不得进行转换。
关键字auto
只是根据返回值推断出类型。因此,它与Python对象不同,例如
# Python
a
a = 10 # OK
a = "10" # OK
a = ClassA() # OK
// C++
auto a; // Unable to deduce variable a
auto a = 10; // OK
a = "10"; // Value of const char* can't be assigned to int
a = ClassA{} // Value of ClassA can't be assigned to int
a = 10.0; // OK, implicit casting warning
由于auto
是在编译过程中得出的,因此在运行时不会有任何缺点。
type()
python中的工作。它推导类型,但不创建该类型的新变量。
decltype
。auto
专门用于变量分配。
如果您问我,到目前为止,这里没有人提到,但就其本身而言,是值得回答的。
由于(即使每个人都应该意识到C != C++
)用C编写的代码可以轻松地设计为C ++代码提供基础,因此无需过多的努力就可以与C ++兼容,因此这是设计的要求。
我知道一些规则,其中一些定义良好的构造C
对其无效,C++
反之亦然。但是,这只会导致可执行文件损坏,并且会应用已知的UB子句,大多数情况下,这种奇怪的循环会导致崩溃或发生任何事情(甚至可能使其未被发现,但这并不重要)。
但是auto
是第一次1这个变化!
假设您之前曾经auto
用作存储类说明符,然后传输了代码。它甚至不一定(取决于使用方式)“中断”;实际上,它可以静默更改程序的行为。
这是应该牢记的。
1 至少是我第一次知道。
int
在C语言中依赖“没有类型暗含”,那么您应该从中得到所有不好的东西。而且,如果您不依赖它,将auto
其与类型一起用作存储类说明符会在C ++中给您带来不错的编译错误(在这种情况下,这是一件好事)。
正如我在此答案中 所描述的,auto
有时可能会导致您不想要的时髦情况。您必须明确地说auto&
要具有引用类型,而这样做auto
只能创建指针类型。通过一起省略说明符,可能导致混淆,从而导致引用的副本而不是实际的引用。
auto
这样,从不推断引用或const
类型。作为auto
参考,您最好使用auto&&
。(通用参考)如果该类型不便宜而不能复制或拥有资源,则该类型一开始就不可复制。
另一个令人讨厌的例子:
for (auto i = 0; i < s.size(); ++i)
生成警告(comparison between signed and unsigned integer expressions [-Wsign-compare]
),因为它i
是带符号的int。为了避免这种情况,您需要编写例如
for (auto i = 0U; i < s.size(); ++i)
也许更好:
for (auto i = 0ULL; i < s.size(); ++i)
size
返回size_t
,您必须能够拥有一个size_t
类似的文字0z
。但是您可以声明一个UDL来做到这一点。(size_t operator""_z(...)
)
unsigned
很有可能不足以容纳std::size_t
主流架构上的所有值,因此,在极少数情况下,如果某人的容器中的元素数量非常庞大,那么使用该容器unsigned
可能会在较低范围内造成无限循环指数。尽管这不太可能成为问题,但std::size_t
应该使用它来获取可以正确表示意图的干净代码。我不确定即使unsigned long long
是严格保证也能满足要求,尽管实际上它必须是相同的。
unsigned long long
保证至少为64位,但size_t
我想理论上可以大于64位。当然,如果您的容器中有> 2 ^ 64个元素,那么您可能有更大的问题要担心... ;-)
我认为auto
在本地化环境中使用是很好的,在这种情况下,读者可以轻松地&显然可以推断出其类型,或者以其类型的注释或可以推断实际类型的名称进行详细记录。那些不了解它如何工作的人可能会以错误的方式使用它,例如使用它代替template
或类似方法。我认为这是一些好用例和坏用例。
void test (const int & a)
{
// b is not const
// b is not a reference
auto b = a;
// b type is decided by the compiler based on value of a
// a is int
}
善用
迭代器
std::vector<boost::tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int> v();
..
std::vector<boost::tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int>::iterator it = v.begin();
// VS
auto vi = v.begin();
功能指针
int test (ClassWithLongName1 a, ClassWithLongName2 b, int c)
{
..
}
..
int (*fp)(ClassWithLongName1, ClassWithLongName2, int) = test;
// VS
auto *f = test;
不良用法
数据流
auto input = "";
..
auto output = test(input);
功能签名
auto test (auto a, auto b, auto c)
{
..
}
琐事
for(auto i = 0; i < 100; i++)
{
..
}
int
,则必须再输入一个字符auto
。这是不可接受的
int
在这里很容易看到,而且输入int
更短。这就是为什么这是一件小事。
我很惊讶没有人提及此事,但是假设您正在计算某些因素的阶乘:
#include <iostream>
using namespace std;
int main() {
auto n = 40;
auto factorial = 1;
for(int i = 1; i <=n; ++i)
{
factorial *= i;
}
cout << "Factorial of " << n << " = " << factorial <<endl;
cout << "Size of factorial: " << sizeof(factorial) << endl;
return 0;
}
此代码将输出以下内容:
Factorial of 40 = 0
Size of factorial: 4
那绝对不是预期的结果。发生这种情况是因为auto
推导了变量阶乘的类型,int
因为它被分配给1
。