Std :: vector nesneleri bir push_back ile kopyalıyor mu?


169

Valgrind ile çok fazla araştırma yapıldıktan sonra, std :: vector'un push_back yapmak istediğiniz bir nesnenin bir kopyasını oluşturduğu sonucuna vardım.

Bu gerçekten doğru mu? Vektör, kopya olmadan bir nesnenin referansını veya işaretçisini tutamaz mı ?!

Teşekkürler


20
Bu temel bir C ++ prensibidir. Nesneler değerlerdir. Atama bir kopyasını oluşturur. Bir nesneyi işaretçi veya başvuru yapmak için *veya ile yapmadığınız sürece aynı nesneye &başvuran iki değişken mümkün değildir .
Daniel Earwicker

8
@DanielEarwicker push_back aslında bir referans alır. Sadece imzadan bir kopya çıkaracağı belli değil.
Brian Gordon

3
@BrianGordon - Öyle demiyor! Bu nedenle yol gösterici ilkeye ihtiyaç vardır. Yine de, imzasından bir şey çıkarabiliriz push_back: a const&. Ya değeri atar (işe yaramaz) ya da bir geri alma yöntemi vardır. Bu yüzden imzasına bakıyoruz backve düz olarak dönüyor &, böylece orijinal değer kopyalandı veya constsessizce atıldı (çok kötü: potansiyel olarak tanımlanmamış davranış). Bu yüzden tasarımcılarının vectorrasyonel olduğunu ( vector<bool>dayanmadığını) varsayarak bunun kopyalar çıkardığı sonucuna varıyoruz.
Daniel Earwicker

Yanıtlar:


183

Evet, std::vector<T>::push_back()argümanın bir kopyasını oluşturur ve vektörde saklar. İşaretçileri vektörünüzdeki nesnelere depolamak istiyorsanız, std::vector<whatever*>yerine bir tane oluşturun std::vector<whatever>.

Ancak, işaretçi tarafından başvurulan nesnelerin, vektör bunlara referans tutarken geçerli kaldığından emin olmanız gerekir (RAII deyimini kullanan akıllı işaretçiler sorunu çözer).


Ayrıca, ham işaretçiler kullanırsanız, artık onlardan sonra temizlik yapmaktan sorumlu olduğunuzu da belirtmek isterim. Bunu yapmak için iyi bir neden yok (yine de düşünebileceğim biri değil), her zaman akıllı bir işaretçi kullanmalısınız.
Ed S.

1
o Eğer daha fazla bilgi için, stl kaplarda std :: auto_ptr kullanmamalısınız, şunları söyledi: neden-olan kullanımlı-it-yanlış-stdauto-ptr-ile-standart-konteynerleri
OriginalCliche

24
C ++ 11 push_backolduğundan, bağımsız değişken bir değer başvurusu ise kopya yerine bir hamle gerçekleştirir. (Nesneler ile değer referanslarına dönüştürülebilir std::move().)
emlai

2
@tuple_cat yorumunuz "argüman bir değerse" yazmalıdır. (Argüman, rvalue referansı olarak beyan edilen bir varlığın adı ise, argüman aslında bir lvalue'dur ve taşınmayacaktır) - bu hatayı başlangıçta yapan "Karl Nicoll" cevabına yaptığım düzenlemeyi kontrol edin
MM

Aşağıda bir cevap var ama açıklığa kavuşturmak için: C ++ 11 aynı zamanda emplace_backherhangi bir kopya veya taşıma işleminden kaçınmak için de kullanılır (kap tarafından sağlanan yerinde nesne oluşturma).
Wormer

34

Evet, std::vectorkopyaları saklar. vectorNesnelerinizin beklenen yaşam sürelerinin ne olduğunu nasıl bilebilirsiniz?

Nesnelerin sahipliğini aktarmak veya paylaşmak istiyorsanız, kaynak yönetimini kolaylaştırmak için muhtemelen shared_ptr( Boost veya TR1'de bulunan ) gibi akıllı işaretçiler kullanın .


3
shared_ptr kullanmayı öğrenin - tam olarak istediğinizi yaparlar. En sevdiğim deyim typedef boost :: shared_ptr <Foo> FooPtr; Sonra FooPtrs kapları yapmak
PM100

3
@ pm100 - Biliyor musunuz boost::ptr_vector?
Manuel

2
Ayrıca class Foo { typedef boost::shared_ptr<Foo> ptr; };sadece yazmak için kullanmayı seviyorum Foo::ptr.
Rupert Jones

2
@ pm100 - shared_ptrtam olarak ateş ve unut değildir. Bkz. Stackoverflow.com/questions/327573 ve stackoverflow.com/questions/701456
Daniel Earwicker

2
shared_ptr, paylaşılan sahipliğiniz varsa iyidir, ancak genellikle aşırı kullanılır. unique_ptr veya boost scoped_ptr, sahiplik açık olduğunda çok daha anlamlı olur.
Nemanja Trifunovic

28

C ++ 11'den itibaren, tüm standart kaplar ( std::vector, std::mapvb.) Taşıma semantiğini destekler, yani artık değerleri standart kaplara geçirebilir ve bir kopyadan kaçınabilirsiniz:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

Alternatif olarak, çoğunlukla aynı etkiyi elde etmek için çeşitli akıllı işaretçiler kullanabilirsiniz:

std::unique_ptr misal

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr misal

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.

2
std::make_uniqueSadece (+ sinir bozucu bir şekilde) yalnızca C ++ 14 ve üstü sürümlerde kullanılabilir olduğunu unutmayın . Bu örnekleri derlemek istiyorsanız derleyicinize standart uygunluğunu ayarlamasını söyleyin.
Laryx Decidua

5a'da auto pFoo =tekrardan kaçınmak için kullanabilirsiniz ; ve tüm std::stringyayınlar kaldırılabilir (dize değişmez değerlerinden örtük dönüşüm vardır std::string)
MM

2
@ user465139 make_uniquekolayca C ++ 11'de uygulanabilir, bu nedenle C ++ 11 derleyicisine sıkışmış biri için hafif bir sıkıntı
MM

1
@MM: Gerçekten. İşte ders kitabı uygulaması:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua

1
@Anakin - Evet, ancak kopyalarsanız yapmalılar. İle std::move()birlikte kullanırsanız std::shared_ptr, orijinal paylaşılan işaretçinin, sahiplik vektöre geçtiği için işaretçisinin değiştirilmesi gerekebilir. Buraya bakın: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll

15

std :: vector her zaman vektörde depolanan her şeyin bir kopyasını oluşturur.

Bir işaretçi vektörü tutuyorsanız, işaretçinin bir kopyasını oluşturur, ancak işaretçinin işaret ettiği örneği oluşturmaz. Büyük nesnelerle uğraşıyorsanız, her zaman bir işaretçi vektörü kullanabilirsiniz (ve muhtemelen kullanmalısınız). Genellikle, uygun tipte bir akıllı işaretçi vektörü kullanmak, güvenlik amaçları için iyidir, çünkü nesne ömrü ve bellek yönetimi işlemek zor olabilir.


3
tipine bağlı değildir. Her zaman bir kopyasını çıkarır. Eğer bir işaretçi işaretleyicinin bir kopyasını
yaparsa

İkiniz de haklısınız. Teknik olarak, evet, her zaman bir kopyasını oluşturur. Pratik olarak, nesneye bir işaretçi iletirseniz, nesneyi değil işaretçiyi kopyalar. Güvenle, uygun bir akıllı işaretçi kullanmalısınız.
Steven Sudit

1
Evet, her zaman kopyalanıyor - Ancak, OP'nin başvurduğu "nesne" büyük olasılıkla bir sınıf veya yapıdır, bu yüzden "Object" in kopyalanıp tanımlanmadığından bahsetmiştim. Yine de kötü ifade.
Reed Copsey

3

Std :: vector, geri ittiğiniz her şeyin bir kopyasını oluşturmakla kalmaz, koleksiyonun tanımı, bunu yapacağını ve bir vektör içinde doğru kopya semantiği olmadan nesneleri kullanamayacağınızı belirtir. Örneğin, bir vektörde auto_ptr kullanmıyorsunuz.


2

C ++ 11 ile ilgili emplaceolan, nesnelerin sahipliğini kaplara taşıyarak aktarmanıza olanak tanıyan üye işlevleri ailesidir.

Kullanım deyimi

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

Lvalue nesnesinin taşınması önemlidir, aksi takdirde bir başvuru veya sabit başvuru olarak iletilir ve taşıma yapıcısı çağrılmaz.


0

kopyaları istemiyorsanız; en iyi yol bir işaretçi vektörü (veya aynı amaca hizmet eden başka bir yapı) kullanmaktır. kopyaları istiyorsanız; doğrudan push_back () kullanın. başka seçeneğin yok.


1
İşaretçi vektörleri hakkında bir not: vector <shared_ptr <obj>>, <obj *> vektöründen çok daha güvenlidir ve shared_ptr, geçen yıl itibarıyla standardın bir parçasıdır.
rich. E

-1

Bunu bulmak için neden çok fazla valgrind soruşturması gerekiyor? Sadece basit bir kodla kendinize kanıtlayın, örn.

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

"Merhaba dünya" basılırsa, nesne kopyalanmış olmalıdır


4
Bu bir kanıt değil. Nesne kopyalanmadıysa, son beyanı tanımsız davranış olur ve olabilir merhaba yazdırın.
Mat

4
doğru test, yerleştirme işleminden sonra ikisinden birini değiştirecektir. Aynı nesne olsaydı (vektör bir referans depoladıysa), her ikisi de değiştirilirdi.
Francesco Dondi
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.