Là std :: vector sao chép các đối tượng với một Push_back?


169

Sau rất nhiều cuộc điều tra với valgrind, tôi đã đưa ra kết luận rằng std :: vector tạo ra một bản sao của một đối tượng mà bạn muốn đẩy_back.

Điều đó có thực sự đúng không? Một vectơ không thể giữ một tham chiếu hoặc một con trỏ của một đối tượng mà không có bản sao?!

Cảm ơn


20
Đây là một nguyên tắc cơ bản của C ++. Đối tượng là các giá trị. Bài tập làm một bản sao. Hai biến tham chiếu đến cùng một đối tượng là không thể trừ khi bạn sửa đổi loại bằng *hoặc &để tạo một con trỏ hoặc tham chiếu.
Daniel Earwicker

8
@DanielEarwicker Push_back thực sự có một tài liệu tham khảo. Không rõ ràng từ chữ ký một mình rằng nó sẽ tạo một bản sao.
Brian Gordon

3
@BrianGordon - Không nói là vậy! Do đó cần có nguyên tắc hướng dẫn. Mặc dù vậy, chúng ta có thể suy ra một cái gì đó từ chữ ký của push_back: phải mất một const&. Hoặc là nó ném giá trị đi (vô dụng), hoặc có một phương thức truy xuất. Vì vậy, chúng tôi xem xét chữ ký của backvà nó trả về đơn giản &, do đó, giá trị ban đầu đã được sao chép hoặc constđã bị bỏ đi một cách âm thầm (rất tệ: hành vi có khả năng không xác định). Vì vậy, giả sử các nhà thiết kế vectorhợp lý ( vector<bool>không chịu được), chúng tôi kết luận nó tạo ra các bản sao.
Daniel Earwicker

Câu trả lời:


183

Có, std::vector<T>::push_back()tạo một bản sao của đối số và lưu trữ nó trong vector. Nếu bạn muốn lưu trữ các con trỏ tới các đối tượng trong vector của mình, hãy tạo một std::vector<whatever*>thay vì std::vector<whatever>.

Tuy nhiên, bạn cần đảm bảo rằng các đối tượng được tham chiếu bởi các con trỏ vẫn còn hiệu lực trong khi vectơ giữ tham chiếu đến chúng (các con trỏ thông minh sử dụng thành ngữ RAII giải quyết vấn đề).


Tôi cũng sẽ lưu ý rằng, nếu bạn sử dụng con trỏ thô, bây giờ bạn có trách nhiệm dọn dẹp sau chúng. Không có lý do chính đáng để làm điều này (dù sao tôi không thể nghĩ ra), bạn nên luôn luôn sử dụng một con trỏ thông minh.
Ed S.

1
điều đó nói rằng, bạn không nên sử dụng std :: auto_ptr trong các thùng chứa stl, để biết thêm thông tin: why-is-it-it-nhầm-to-use-stdauto-ptr-with-standard-container
OriginalCliche

24
Vì C ++ 11, push_backsẽ thực hiện di chuyển thay vì bản sao nếu đối số là tham chiếu giá trị. (Các đối tượng có thể được chuyển đổi thành tham chiếu giá trị với std::move().)
emlai

2
@tuple_cat bình luận của bạn sẽ nói "nếu đối số là giá trị". (Nếu đối số là tên của một thực thể được khai báo là tham chiếu giá trị, thì đối số thực sự là một giá trị và sẽ không được chuyển từ) - kiểm tra chỉnh sửa của tôi với câu trả lời của "Karl Nicoll" ban đầu đã mắc lỗi đó
MM

Có một câu trả lời dưới đây nhưng để làm rõ: Vì C ++ 11 cũng sử dụng emplace_backđể tránh mọi bản sao hoặc di chuyển (xây dựng đối tượng tại chỗ được cung cấp bởi container).
Wormer

34

Có, std::vectorlưu trữ các bản sao. Làm thế nào để vectorbiết thời gian sống dự kiến ​​của các đối tượng của bạn là gì?

Nếu bạn muốn chuyển hoặc chia sẻ quyền sở hữu đối tượng, hãy sử dụng các con trỏ, có thể là các con trỏ thông minh như shared_ptr(được tìm thấy trong Boost hoặc TR1 ) để dễ dàng quản lý tài nguyên.


3
học cách sử dụng shared_ptr - họ làm chính xác những gì bạn muốn. Thành ngữ yêu thích của tôi là typedef boost :: shared_ptr <Foo> FooPtr; Sau đó, tạo các thùng chứa FooPtrs
pm100

3
@ pm100 - Bạn có biết boost::ptr_vector?
Manuel

2
Tôi cũng thích sử dụng class Foo { typedef boost::shared_ptr<Foo> ptr; };để chỉ viết Foo::ptr.
Rupert Jones

2
@ pm100 - shared_ptrkhông chính xác là lửa và quên. Xem stackoverflow.com/questions/327573stackoverflow.com/questions/701456
Daniel Earwicker

2
shared_ptr là tốt nếu bạn có quyền sở hữu chung, nhưng nó thường được sử dụng quá mức. unique_ptr hoặc boost scoped_ptr có ý nghĩa hơn nhiều khi quyền sở hữu rõ ràng.
Nemanja Trifunovic

28

Từ C ++ 11 trở đi, tất cả các bộ chứa tiêu chuẩn ( std::vector, std::mapv.v.) đều hỗ trợ ngữ nghĩa di chuyển, nghĩa là bây giờ bạn có thể chuyển các giá trị cho các bộ chứa tiêu chuẩn và tránh một bản sao:

// 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");

Ngoài ra, bạn có thể sử dụng các con trỏ thông minh khác nhau để có được hiệu quả tương tự:

std::unique_ptr thí dụ

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 thí dụ

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
Lưu ý rằng std::make_unique(thật khó chịu) chỉ có sẵn trong C ++ 14 trở lên. Hãy chắc chắn rằng bạn nói với trình biên dịch của bạn để thiết lập sự phù hợp tiêu chuẩn của nó cho phù hợp nếu bạn muốn biên dịch các ví dụ này.
Laryx Decidua

Trong 5a bạn có thể sử dụng auto pFoo =để tránh lặp lại; và tất cả các std::stringphôi có thể được loại bỏ (có chuyển đổi ngầm định từ chuỗi ký tự thành std::string)
MM

2
@ user465139 make_uniquecó thể dễ dàng được triển khai trong C ++ 11, do đó, chỉ có một chút khó chịu đối với người bị mắc kẹt với trình biên dịch C ++ 11
MM

1
@MM: Thật vậy. Dưới đây là triển khai sách giáo khoa:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua

1
@Anakin - Có, họ nên làm, nhưng chỉ khi bạn sao chép. Nếu bạn sử dụng std::move()với std::shared_ptr, con trỏ dùng chung ban đầu có thể con trỏ của nó bị thay đổi do quyền sở hữu được truyền cho vectơ. Xem tại đây: coliru.stacked-crooking.com/a/99d4f04f05e5c7f3
Karl Nicoll

15

std :: vector luôn tạo một bản sao của bất cứ thứ gì đang được lưu trữ trong vector.

Nếu bạn đang giữ một vectơ con trỏ, thì nó sẽ tạo một bản sao của con trỏ, nhưng không phải là trường hợp mà con trỏ đang trỏ tới. Nếu bạn đang xử lý các đối tượng lớn, bạn có thể (và có lẽ nên) luôn sử dụng một vectơ con trỏ. Thông thường, sử dụng một vectơ con trỏ thông minh thuộc loại thích hợp là tốt cho mục đích an toàn, vì việc xử lý tuổi thọ của đối tượng và quản lý bộ nhớ có thể khó khăn.


3
Nó không phụ thuộc vào loại. Nó luôn luôn tạo ra một bản sao. Nếu con trỏ của nó tạo một bản sao của con trỏ
pm100

Bạn đều đúng cả. Về mặt kỹ thuật, vâng, nó luôn tạo ra một bản sao. Thực tế, nếu bạn truyền cho nó một con trỏ tới đối tượng, nó sẽ sao chép con trỏ chứ không phải đối tượng. Một cách an toàn, bạn nên sử dụng một con trỏ thông minh thích hợp.
Steven Sudit

1
Có, nó luôn luôn sao chép - Tuy nhiên, "đối tượng" mà OP đang đề cập rất có thể là một lớp hoặc cấu trúc, vì vậy tôi đã đề cập đến việc nó sao chép "Đối tượng" hay không phụ thuộc vào định nghĩa. Mặc dù nói kém.
Sậy Copsey

3

Std :: vector không chỉ tạo ra một bản sao của bất cứ thứ gì bạn đang đẩy lùi, mà định nghĩa của bộ sưu tập nói rằng nó sẽ làm như vậy, và bạn không thể sử dụng các đối tượng nếu không có ngữ nghĩa sao chép chính xác trong một vectơ. Vì vậy, ví dụ, bạn không sử dụng auto_ptr trong một vectơ.


2

Có liên quan trong C ++ 11 là emplacehọ các hàm thành viên, cho phép bạn chuyển quyền sở hữu các đối tượng bằng cách di chuyển chúng vào các thùng chứa.

Thành ngữ sử dụng sẽ như thế nào

std::vector<Object> objs;

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

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

Việc di chuyển cho đối tượng lvalue rất quan trọng vì nếu không nó sẽ được chuyển tiếp dưới dạng tham chiếu hoặc tham chiếu const và hàm tạo di chuyển sẽ không được gọi.


0

nếu bạn không muốn các bản sao; thì cách tốt nhất là sử dụng một vectơ con trỏ (hoặc một cấu trúc khác phục vụ cho cùng một mục tiêu). nếu bạn muốn các bản sao; sử dụng trực tiếp push_back (). bạn không có lựa chọn nào khác.


1
Một lưu ý về vectơ con trỏ: vectơ <shared_ptr <obj >> an toàn hơn vectơ <obj *> và shared_ptr là một phần của tiêu chuẩn vào năm ngoái.
giàu.e

-1

Tại sao phải mất rất nhiều cuộc điều tra valgrind để tìm ra điều này! Chỉ cần chứng minh điều đó với chính mình bằng một số mã đơn giản, vd

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

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

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

Nếu "hello world" được in, đối tượng phải được sao chép


4
Đây không phải là một bằng chứng. Nếu đối tượng không được sao chép, câu lệnh cuối cùng của bạn sẽ là hành vi không xác định và có thể in lời chào.
Mat

4
kiểm tra chính xác sẽ sửa đổi một trong hai sau khi chèn. Nếu chúng là cùng một đối tượng (nếu vectơ lưu trữ một tham chiếu), cả hai sẽ được sửa đổi.
Francesco Dondi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.