C ++ 'da ilk vektör kapasitesi


92

Ne capacity()bir bir std::vectorvarsayılan constuctor kullanılarak oluşturulduğu? size()Sıfır olduğunu biliyorum . Varsayılan olarak oluşturulmuş bir vektörün yığın bellek ayırma çağırmadığını söyleyebilir miyiz?

Bu şekilde, tek bir ayırma kullanarak keyfi bir rezerve sahip bir dizi oluşturmak mümkün olacaktır std::vector<int> iv; iv.reserve(2345);. Diyelim ki bir sebepten dolayı size()2345'ten başlamak istemiyorum .

Örneğin, Linux'ta (g ++ 4.4.5, çekirdek 2.6.32 amd64)

#include <iostream>
#include <vector>

int main()
{
  using namespace std;
  cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl;
  return 0;
}

basılı 0,10. Bu bir kural mı yoksa STL satıcısına mı bağlı?


7
Standart, vektörün başlangıç ​​kapasitesi hakkında hiçbir şey belirtmez, ancak çoğu uygulama 0 kullanır.
Bay Anubis

11
Garanti yok, ancak ben herhangi bir talepte bulunmadan bellek ayıran herhangi bir uygulamanın kalitesini ciddi olarak sorgulayacağım.
Mike Seymour

2
@MikeSeymour Katılmıyorum. Gerçekten yüksek performanslı bir uygulama, küçük bir satır içi arabellek içerebilir, bu durumda başlangıç ​​kapasitesini () buna ayarlamak mantıklı olacaktır.
alastair

6
@alastair swapTüm yineleyicileri kullanırken ve referanslar geçerli kalır ( end()s hariç ). Bu, satır içi bir arabelleğin mümkün olmadığı anlamına gelir.
Notinlist

Yanıtlar:


74

Standart, capacitybir konteynerin baş harfinin ne olması gerektiğini belirtmez , bu nedenle uygulamaya güveniyorsunuz. Ortak bir uygulama kapasiteyi sıfırdan başlatır, ancak bunun garantisi yoktur. Öte yandan stratejinizi daha iyi hale getirmenin bir yolu yok, std::vector<int> iv; iv.reserve(2345);bu yüzden ona bağlı kalın.


1
Son ifadenizi almıyorum. Başlangıçta 0 olma kapasitesine güvenemiyorsanız, vektörünüzün bir başlangıç ​​boyutuna sahip olmasına izin vermek için programınızı yeniden yapılandırabilirsiniz. Bu, yığın bellek isteklerinin yarısı kadar olur (2'den 1'e).
bitmask

4
@bitmask: Pratik olmak: bir vektörün varsayılan kurucuda bellek ayırdığı herhangi bir uygulama biliyor musunuz ? Standart tarafından garanti edilmemektedir, ancak Mike Seymour'un da işaret ettiği gibi ihtiyaç olmadan bir tahsisin tetiklenmesi , uygulamanın kalitesi açısından kötü bir koku olacaktır .
David Rodríguez - dribeas

3
@ DavidRod Rodríguez-dribeas: Konu bu değil. Öncül "Bu kadar orada olmadığını merak rahatsız etmeyin, geçerli bir strateji daha iyi yapamaz oldu belki aptal uygulamalar olmak". Önerme "böyle bir uygulama yok, bu yüzden zahmet etmeyin" olsaydı, satın alırdım. Sonuç doğru olur, ancak sonuç işe yaramaz. Özür dilerim, belki de nitelendiriyorum.
bitmask

3
@bitmask Varsayılan yapıda bellek ayıran bir uygulama varsa, söylediğiniz şeyi yapmak ayırma sayısını yarıya indirecektir. Ancak vector::reserve, başlangıç ​​boyutunu belirtmekle aynı şey değildir. Bir başlangıç ​​boyut değeri alan / kopyalayan vektör oluşturucular, nnesneleri başlatır ve dolayısıyla doğrusal karmaşıklığa sahiptir. Otoh, çağıran rezervi sadece hareketli / kopyalama demektir size()elemanları eğer bir yeniden tahsis tetiklenir. Boş bir vektörde kopyalanacak hiçbir şey yoktur. Bu nedenle, uygulama varsayılan olarak yapılandırılmış bir vektör için bellek ayırsa bile ikincisi arzu edilebilir.
Praetorian

4
@bitmask, bu dereceye kadar tahsisler konusunda endişeleriniz varsa, o zaman belirli standart kitaplığınızın uygulamasına bakmalı ve spekülasyona güvenmemelisiniz.
Mark Ransom

36

Std :: vector depolama uygulamaları önemli ölçüde değişiklik gösterir, ancak karşılaştığım tüm uygulamalar 0'dan başlar.

Aşağıdaki kod:

#include <iostream>
#include <vector>

int main()
{
  using namespace std;

  vector<int> normal;
  cout << normal.capacity() << endl;

  for (unsigned int loop = 0; loop != 10; ++loop)
  {
      normal.push_back(1);
      cout << normal.capacity() << endl;
  }

  cin.get();
  return 0;
}

Aşağıdaki çıktıyı verir:

0
1
2
4
4
8
8
8
8
16
16

GCC 5.1 kapsamında ve:

0
1
2
3
4
6
6
9
9
9
13

MSVC 2013 kapsamında.


3
Bu çok küçümsenmiş @Andrew
Valentin Mercier

Eh sen ... seyrek verileri içeren herhangi bir şey yapıyoruz eğer öyleyse, neredeyse her sadece bir vektör kullanılması hız amaçlı öneri hemen her zaman bulmak
Andrew

@Andrew buna ne başlamalıydı? Programcı varsayılandan daha fazlasını ayırmak istiyorsa, herhangi bir şeyi ayırmak, bu belleği ayırmak ve ayırmak için zaman kaybetmek olur. 1 ile başlamaları gerektiğini varsayıyorsanız, yine de birisi 1 tahsis eder etmez bunu tahsis edecektir.
Puddle

@ Su birikintisi Görünüş değerinde almak yerine satır aralarını okuyorsunuz. Bunun alay olmadığına dair ipucu, "akıllı" kelimesinin yanı sıra seyrek veriden bahseden ikinci yorumumdur.
Andrew

@Andrew Oh güzel, yeterince rahatladın, 0'da başlattılar. Neden bu konuda şaka yollu yorum yapasınız ki?
Puddle

7

Standardı anladığım kadarıyla (aslında bir referansın adını veremiyor olsam da), kapsayıcı dayanağı ve bellek tahsisi iyi bir nedenden dolayı kasıtlı olarak ayrıştırıldı. Bunun için farklı, ayrı çağrılarınız var

  • constructor kabın kendisini yaratmak için
  • reserve() en az (!) belirli sayıda nesneyi barındırmak için uygun büyüklükte bir bellek bloğunu önceden tahsis etmek

Ve bu çok mantıklı. Var olmanın tek hakkı reserve(), vektörü büyütürken muhtemelen pahalı yeniden tahsisler etrafında kodlama fırsatı vermektir. Faydalı olabilmeniz için, saklanacak nesnelerin sayısını bilmeniz veya en azından eğitimli bir tahminde bulunabilmeniz gerekir. Bu verilmezse reserve(), boşa harcanan bellek için yeniden tahsisi değiştireceğinizden uzak durmanız daha iyi olur.

Yani hepsini bir araya getirirsek:

  • Standart kasıtlı yok değil (bir uygulama özgü başlık altında, sabit "bir şey" ayırmak yerine en azından daha da cazip olacaktır) nesnelerin belirli sayıda bir bellek bloğu tahsis öncesi sağlayan bir yapıcı belirtin.
  • Tahsis örtük olmamalıdır. Bu nedenle, bir bloğu önceden tahsis etmek için ayrı bir çağrı yapmanız gerekir reserve()ve bunun aynı inşaat yerinde olması gerekmez (yerleştirmek için gerekli büyüklüğün farkına vardıktan sonra tabii ki daha sonra olabilir / olmalı)
  • Dolayısıyla, bir vektör her zaman uygulama tanımlı boyutta bir bellek bloğunu önceden tahsis ederse, bu amaçlanan işi engeller, reserve()değil mi?
  • STL doğal olarak bir vektörün amaçlanan amacını ve beklenen boyutunu bilemezse, bir bloğu önceden tahsis etmenin avantajı ne olur? Ters etki yaratmasa da, oldukça saçma olacaktır.
  • Bunun yerine uygun çözüm, push_back()önceden tarafından açıkça tahsis edilmemişse , belirli bloğu ilk ile ayırmak ve uygulamaktır reserve().
  • Gerekli bir yeniden tahsis durumunda, blok boyutundaki artış da uygulamaya özgüdür. Bildiğim vektör uygulamaları, boyutta üssel bir artışla başlıyor, ancak büyük miktarda bellek israfını ve hatta onu patlatmayı önlemek için artış hızını belirli bir maksimumda sınırlayacak.

Tüm bunlar, yalnızca tahsis eden bir kurucu tarafından rahatsız edilmediği takdirde tam operasyon ve avantaj sağlar. reserve()(Ve shrink_to_fit()) tarafından talep üzerine geçersiz kılınabilen yaygın senaryolar için makul varsayılan değerleriniz var . Dolayısıyla, standart açıkça belirtmese bile, yeni inşa edilmiş bir vektörün önceden tahsis etmediğini varsaymanın mevcut tüm uygulamalar için oldukça güvenli bir bahis olduğuna oldukça eminim.


4

Diğer yanıtlara ufak bir ek olarak, Visual Studio ile hata ayıklama koşulları altında çalışırken, kapasite sıfırdan başlamasına rağmen, varsayılan olarak yapılandırılmış bir vektörün yine de yığın üzerinde tahsis edeceğini buldum.

Özellikle _ITERATOR_DEBUG_LEVEL! = 0 ise, vektör yineleyici kontrolüne yardımcı olmak için biraz alan ayıracaktır.

https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level

O sırada özel bir ayırıcı kullandığım ve fazladan tahsis beklemediğim için bunu biraz can sıkıcı buldum.


İlginçtir, istisnasız garantileri ihlal ediyorlar (en azından C + 17 için, daha önce mi?): En.cppreference.com/w/cpp/container/vector/vector
Deduplicator

4

Bu eski bir sorudur ve buradaki tüm cevaplar, standardın bakış açısını ve kullanarak taşınabilir bir şekilde başlangıç ​​kapasitesini nasıl elde edebileceğinizi doğru bir şekilde açıklamıştır std::vector::reserve;

Bununla birlikte, herhangi bir STL uygulamasının bir std::vector<T>nesnenin inşası üzerine bellek ayırmasının neden mantıklı olmadığını açıklayacağım ;

  1. std::vector<T> eksik tiplerin;

    C ++ 17'den önce, std::vector<T>eğer Tsomutlama noktasında tanımı hala bilinmiyorsa, a oluşturmak tanımsız bir davranıştır . Ancak bu kısıtlama C ++ 17'de gevşetildi .

    Bir nesneye verimli bir şekilde bellek ayırmak için boyutunu bilmeniz gerekir. C ++ 17'den itibaren, müşterileriniz std::vector<T>sınıfınızın .NET Framework boyutunu bilmediği durumlarda olabilir T. Tür tamlığına bağlı bellek ayırma özelliklerine sahip olmak mantıklı mı?

  2. Unwanted Memory allocations

    Yazılımda bir grafiğin modeline ihtiyaç duyacağınız birçok, çok, birçok kez vardır. (Ağaç bir grafiktir); Büyük olasılıkla şöyle modelleyeceksiniz:

    class Node {
        ....
        std::vector<Node> children; //or std::vector< *some pointer type* > children;
        ....
     };
    

    Şimdi bir an düşünün ve çok sayıda terminal düğümünüz olup olmadığını hayal edin. STL uygulamanız, sadece içinde nesnelerin olması beklentisiyle fazladan bellek ayırırsa çok sinirlenirsiniz children.

    Bu sadece bir örnek, daha fazlasını düşünmekten çekinmeyin ...


2

Standart kapasite için başlangıç ​​değerini belirtmez, ancak maksimum boyutu aşmamanız koşuluyla STL kabı, koyduğunuz kadar çok veriyi barındıracak şekilde otomatik olarak büyür (bilmek için max_size üye işlevini kullanın). Vektör ve dizi için, daha fazla alana ihtiyaç duyulduğunda büyüme yeniden tahsis ile ele alınır. 1-1000 değerini tutan bir vektör oluşturmak istediğinizi varsayalım. Rezerv kullanmadan, kod genellikle aşağıdaki döngü sırasında 2 ile 18 arasında yeniden tahsisle sonuçlanır:

vector<int> v;
for ( int i = 1; i <= 1000; i++) v.push_back(i);

Rezerv kullanmak için kodu değiştirmek döngü sırasında 0 ayırmaya neden olabilir:

vector<int> v;
v.reserve(1000);

for ( int i = 1; i <= 1000; i++) v.push_back(i);

Kabaca söylemek gerekirse, vektör ve dizi kapasiteleri her seferinde 1,5 ile 2 arasında bir faktör artar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.