考虑下面的示例代码。它有一个太大而无法复制的数据结构MyDataStructure
和一个小的迭代器类MyDataIterator
,调用者可以实例化该类来迭代结构中的数据。
在代码的玩具版本中,数据结构仅包含 10 个整数(1000-1009),并且WORKING CODE
其中的部分main()
按照预期正确地对它们进行迭代。
但是,中的第二个(BROKEN CODE
)部分main()
无法正确地对它们进行迭代;它调用了未定义的行为,因为它调用了GetDataStructureByValue()
而不是GetDataStructureByReference()
,这意味着存储在其私有成员变量中MyDataStructure
的引用在第一次循环迭代之前变为悬垂引用。iter
_ds
这是一个有点阴险的问题,因为在非玩具代码中,并不总是很容易从函数名称中分辨/记住它是按值返回还是按引用返回,但前一种情况会导致不明显的运行时错误,而后一种情况则工作正常。
我的问题是,是否有任何合理的方法可以让现代 C++ 编译器报错或至少警告有问题的情况?我怀疑没有,但我还是要问,因为如果可以以某种方式自动标记此问题的实例,我会感到更安全。
该玩具程序的源代码如下,后面是我在运行 XCode 的 Mac 上输出的示例:
#include <stdio.h>
class MyDataStructure
{
public:
MyDataStructure()
{
printf("MyDataStructure CTOR %p\n", this);
for (int i=0; i<10; i++) _data[i] = i+1000;
}
~MyDataStructure()
{
printf("MyDataStructure DTOR %p\n", this);
for (int i=0; i<10; i++) _data[i] = -1; // just to make the symptoms more obvious
}
bool IsPositionValid(int pos) const {return ((pos >= 0)&&(pos < 10));}
int GetValueAt(int pos) const {return _data[pos];}
private:
int _data[10];
};
const MyDataStructure & GetDataStructureByReference()
{
static MyDataStructure _ds;
return _ds;
}
MyDataStructure GetDataStructureByValue()
{
MyDataStructure _ds;
return _ds;
}
class MyDataIterator
{
public:
MyDataIterator(const MyDataStructure & ds) : _ds(ds), _idx(0) {/* empty */}
bool HasValue() const {return _ds.IsPositionValid(_idx);}
int GetValue() const {return _ds.GetValueAt(_idx);}
void operator++(int) {_idx++;}
const MyDataStructure & GetDataStructure() const {return _ds;}
private:
const MyDataStructure & _ds;
int _idx;
};
int main(int, char **)
{
{
// The following line of code is okay; it counts from 1000 to 10009, as expected
printf("WORKING CODE:\n");
for (MyDataIterator iter(GetDataStructureByReference()); iter.HasValue(); iter++) printf("iter returned %i from %p\n", iter.GetValue(), &iter.GetDataStructure());
}
{
// The following line of code is buggy because, the MyDataStructure object that (iter) keeps a reference to
// gets destroyed before the first iteration of the loop.
printf("\nBROKEN CODE:\n");
for (MyDataIterator iter(GetDataStructureByValue()); iter.HasValue(); iter++) printf("iter returned %i from %p\n", iter.GetValue(), &iter.GetDataStructure());
}
printf("\nEND OF PROGRAM\n");
return 0;
}
...示例输出:
$ ./a.out
WORKING CODE:
MyDataStructure CTOR 0x1015c4000
iter returned 1000 from 0x1015c4000
iter returned 1001 from 0x1015c4000
iter returned 1002 from 0x1015c4000
iter returned 1003 from 0x1015c4000
iter returned 1004 from 0x1015c4000
iter returned 1005 from 0x1015c4000
iter returned 1006 from 0x1015c4000
iter returned 1007 from 0x1015c4000
iter returned 1008 from 0x1015c4000
iter returned 1009 from 0x1015c4000
BROKEN CODE:
MyDataStructure CTOR 0x7ff7be93d198
MyDataStructure DTOR 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
iter returned -1 from 0x7ff7be93d198
END OF PROGRAM
MyDataStructure DTOR 0x1015c4000
一个简单的做法就是防止使用临时对象创建迭代器。如果你
MyDataIterator
使用以下签名添加另一个构造函数然后,这个构造函数将被重载解析选中,因为它更适合右值。由于它已被删除,编译器将发出错误,因为您无法调用已删除的构造函数。您可以在此实例中看到它“工作”(生成错误) 。