C++ 为什么不是';t std::list.size()常量时间?

C++ 为什么不是';t std::list.size()常量时间?,c++,performance,list,size,C++,Performance,List,Size,此代码运行了0.012秒: std::list<int> list; list.resize(100); int size; for(int i = 0 ; i < 10000; i++) size = list.size(); std::list; 列表。调整大小(100); 整数大小; 对于(int i=0;i

此代码运行了0.012秒:

 std::list<int> list;
 list.resize(100);
 int size;
 for(int i = 0 ; i < 10000; i++)
     size = list.size();
std::list;
列表。调整大小(100);
整数大小;
对于(int i=0;i<10000;i++)
size=list.size();
这一次持续9.378秒:

 std::list<int> list;
 list.resize(100000);
 int size;
 for(int i = 0 ; i < 10000; i++)
     size = list.size();
std::list;
列表。调整大小(100000);
整数大小;
对于(int i=0;i<10000;i++)
size=list.size();

在我看来,以这种方式实现std::list是可能的,大小将存储在一个私有变量中,但根据这种方式,每次调用size时都会再次计算它。有人能解释为什么吗?

恒定时间
size()
和恒定时间
列表之间存在冲突。委员会选择支持
拼接

在两个列表之间拼接节点时,必须对移动的节点进行计数,以更新两个列表的大小。只需更改几个内部指针,就可以消除拼接节点的许多优势


正如评论中所指出的,C++11已经改变了这一点,它放弃了O(1)来使用
splice

void splice(const_iterator position, list& x, const_iterator first, const_iterator last);
void splice(const_iterator position, list&& x, const_iterator first, const_iterator last);
复杂性:如果
&x==此
,则为常数时间;否则,线性时间


在ISO/IEC 14882:2011的§C.2.12第23条:“容器库”中:

更改:size()成员函数的复杂性现在保持不变

理由:缺乏对size()复杂性的说明导致了不同的实现和不一致的性能特征

对原始特征的影响:符合C++ 2003的一些容器实现可能不符合本标准中的指定siz()要求。根据更严格的要求调整容器(如std::list)可能需要不兼容的更改


有关评论:

在23.3.5.5“列表操作”中,同样在ISO/IEC 14882:2011中:

列表提供了三种拼接操作,可以将元素从一个列表破坏性地移动到另一个列表。如果get_allocator()!=x、 获取分配程序()

无效拼接(常量迭代器位置、列表和x)
无效拼接(常量迭代器位置、列表和x)
需要:&x!=这个。
效果:在位置之前插入x的内容,x变为空。现在,指向x的移动元素的指针和引用引用引用了这些相同的元素,但它们是*this的成员。引用移动元素的迭代器将继续引用它们的元素,但它们现在的行为就像是*this的迭代器,而不是x的迭代器。
复杂性:恒定时间

无效拼接(常量迭代器位置、列表和x、常量迭代器i)
无效拼接(常量迭代器位置、列表和x、常量迭代器i)
效果:在位置之前从列表x插入i指向的元素,并从x中删除该元素。如果位置==i或位置==++i,则结果不变。指向*的指针和引用我继续引用同一个元素,但作为*的成员。*i的迭代器(包括i本身)继续引用相同的元素,但现在的行为就像*this的迭代器,而不是x的迭代器。
需要:i是x的有效可取消引用迭代器。
复杂性:恒定时间

无效拼接(常量迭代器位置、列表和x、常量迭代器优先、常量迭代器最后)
无效拼接(常量迭代器位置、列表和x、常量迭代器第一、常量迭代器最后)
效果:在“位置”之前的[first,last]范围内插入元素,并从x中删除元素。 要求:[first,last)是x中的有效范围。如果position是范围中的迭代器,则结果未定义[第一,最后)。指向x的移动元素的指针和引用现在指的是那些相同的元素,但它们是*this的成员。指向移动元素的迭代器将继续指向它们的元素,但它们现在的行为是*this的迭代器,而不是x的迭代器。
复杂性:如果&x==此,则为常数时间;否则为线性时间


您不需要list::size,它不是一个随机访问容器。计算元素数的唯一方法是迭代整个列表。私有变量会增加内存开销,这仍然是一个问题-如果不是对您个人,那么对许多其他人来说也是一个问题。您确实隔离了list.size()吗在您的测量中循环?或者是行列表。根据您的问题的严格阅读,resize(…)也包括在其中?如果我没有说清楚,很抱歉。仅list.size()在循环中。你到底在测量什么?整个程序,还是循环?一个好的编译器会删除代码中的“循环”部分,因为它只是一遍又一遍地执行相同的操作。@ViktorLatypov让我看看我是否正确理解你的意思……你是在断言那些会在额外的4分钟内对你表示异议的人每个std::list(或8个字节,甚至16个字节)实际上会使用std::list作为开头?你是认真的吗?我的意思是真的吗?Committee实际上说这取决于实现。GNU选择将其实现为O(n)。在MS STL中,它是O(1)。在C++11中,它保证是O(1)大小可以缓存,但这不是很有用,因为你不知道什么时候会发生。否则你会得到“有时是O(1),有时是O(n)”。这不是很有用。aleguna说的。C++03没有选择(它说
Size()
“应该”是
O(1)
,但不是“必须”)。C++11选择了
Size()
,这使得
列表
与所有其他标准容器保持一致,这些标准容器有明显的方法来实现O(1)
size()
。在C++03和C++11中,
拼接
只需要在大小(如果缓存)不变的情况下具有恒定的复杂性可以在不计算节点的情况下进行更新。委员会没有强制规定可预测的行为,而是让每个人都面临两个世界中最糟糕的情况。当然,他们应该指定大小为O(1)。我