除了上述情况和必须使用引用的情况外,还有其他原因/用例可以使用引用而不是所谓的“已经存在的变量”吗?
注:我想知道在不调用函数(我已经知道了)的情况下,引用还有哪些其他用途或用例?我还特别想知道在什么情况下引用优于原始变量,而不是优于指针(在选择两者之间时,另一个问题已经告诉我“尽可能使用引用,必须使用指针”)。
假设您有一个很大的结构,或者是std::vector
或std::string
对象,并且您通过值将其传递给函数。这意味着该对象被复制,对于大型对象(例如数百万个条目的向量)可能相当低效。然后,您可以使用对常量对象的引用来传递,例如 std::vector<SomeType> const& my_object
。
尽可能使用引用而不是指针。除非您想操作低级内存,否则不应处理指针。另一个用途是使用new作为指针在堆上保留主数据,并传递该指针的引用。但是,使用new unique_ptr、shared_ptr和weak_ptr组合甚至都不需要这样做。唯一需要指针的地方是当您的对象可以为nullptr时,因为引用不能为NULL。您可以安全地假设引用是有效的对象,除非有人以变态的方式扭曲了代码。
普遍认为引用比指针更安全,但这是一种误解。产生空引用和其他非法引用同样容易。这些问题可能会在程序崩溃之前传播相当远。以下是一些例子:
创建空引用:只需取消引用空指针以将结果传递给期望引用的内容。
class Foo {
public:
Foo() : bar(null_ptr) {}
void setSize(size_t newSize) {
delete bar;
bar = new int[newSize];
}
doSomething() {
myVec.push_back(bar[0]); //这将在push_back()实现的某个地方崩溃,而不是在此处!
}
private:
int* bar;
std::vector<int> myVec;
};
void baz() {
Foo myFoo;
myFoo.doSomething();
}
重点是上面的代码不会在您取消引用空指针的地方崩溃,它只会悄悄地创建一个空引用。这是合法的,因为将空指针取消引用被定义为未定义行为,并且创建空引用是编译器可以执行的有效操作。
空引用将成功传递给std::vector<>::push_back()
,它仅在它自己尝试使用引用实际获取其后面的值时才会导致段错误。
悬空引用:让引用对象在引用结束之前结束其生命周期即可。同样,与指针相同的问题:
std::vector<int> foo;
foo->push_back(7);
int& bar = foo[0]; //完全合法
bar *= 6; //完全合法
foo->push_back(8); //完全合法
cout << "The answer is: " << bar; //BOOM,bar是悬空的,导致未定义行为的结果
注意:此代码实际上可能按预期运行,就像访问悬空指针的代码可能可重复地按预期运行一样。
超出范围的引用:与指针一样容易实现:
std::vector<int> array;
array.resize(7);
int& bar = array[7]; //未定义行为,即使编译器检测到这一点,它也会悄悄地忽略它
bar = 42; //由于这是未定义行为,编译器可能会优化掉这一行
与悬空引用一样,此代码可能完全正常运行,但可能不会执行您认为它执行的操作。由于优化,它甚至可能不会写入超出范围的元素。或者它可能会破坏堆栈,或者两者都会默默地计算错误的结果。在这里只是崩溃并不是最不可能的结果。
正如您所看到的,最后两个示例代码甚至不需要显式指针,它们仅使用现代、闪亮的C++风格数据结构。然而,它们会导致未定义的行为,在调用它的地方不会引起注意。非法引用可能从创建它的地方传播任何距离,然后才会开始发生明显的糟糕事情。这正是人们害怕使用指针的原因;指针和引用的危险是相同的。
因此,真正有意义的地方使用引用。但不要依赖于它们是“安全”的。
if (x->is_a_foo()) { foo& xf=static_cast<foo&>(*x); xf.memberof_foo(...)...}
- JSFT& r=a[++i].b[--j].m_t; ... r(a)+r(b) ...
假设适当的T::operator()
,我们希望有一种不混乱的方法来调用它两次(而且不会两次修改 i 或 j)。 - JSF