Tôi tin rằng bạn có quan sát chính xác nhưng giải thích sai!
Việc sao chép sẽ không xảy ra bằng cách trả về giá trị, bởi vì mọi trình biên dịch thông minh thông thường sẽ sử dụng (N) RVO trong trường hợp này. Từ C ++ 17, điều này là bắt buộc, vì vậy bạn không thể thấy bất kỳ bản sao nào bằng cách trả về một vectơ được tạo cục bộ từ hàm.
OK, hãy chơi một chút với std::vector
và những gì sẽ xảy ra trong quá trình xây dựng hoặc bằng cách điền từng bước một.
Trước hết, hãy tạo một kiểu dữ liệu giúp mọi bản sao hoặc di chuyển hiển thị như thế này:
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
Và bây giờ hãy bắt đầu một số thử nghiệm:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
Những gì chúng ta có thể quan sát:
Ví dụ 1) Chúng tôi tạo một vectơ từ danh sách khởi tạo và có thể chúng tôi hy vọng rằng chúng tôi sẽ thấy cấu trúc 4 lần và 4 lần di chuyển. Nhưng chúng tôi nhận được 4 bản! Nghe có vẻ hơi bí ẩn, nhưng lý do là việc thực hiện danh sách khởi tạo! Đơn giản là nó không được phép di chuyển khỏi danh sách vì iterator từ danh sách là const T*
điều khiến cho không thể di chuyển các phần tử từ nó. Một câu trả lời chi tiết về chủ đề này có thể được tìm thấy ở đây: initizer_list và di chuyển ngữ nghĩa
Ví dụ 2) Trong trường hợp này, chúng tôi nhận được một bản dựng ban đầu và 4 bản sao của giá trị. Điều đó không có gì đặc biệt và là những gì chúng ta có thể mong đợi.
Ví dụ 3) Cũng ở đây, chúng tôi xây dựng và một số di chuyển như mong đợi. Với việc thực hiện stl của tôi, vectơ tăng theo hệ số 2 mỗi lần. Vì vậy, chúng ta thấy một cấu trúc đầu tiên, một cấu trúc khác và vì vectơ thay đổi kích thước từ 1 đến 2, chúng ta thấy sự di chuyển của phần tử đầu tiên. Trong khi thêm 3 cái, chúng ta thấy thay đổi kích thước từ 2 thành 4 cần di chuyển hai yếu tố đầu tiên. Tất cả như mong đợi!
Ví dụ 4) Bây giờ chúng tôi dự trữ không gian và điền vào sau. Bây giờ chúng tôi không có bản sao và không di chuyển nữa!
Trong mọi trường hợp, chúng tôi không thấy bất kỳ động thái nào cũng như sao chép bằng cách trả lại vectơ cho người gọi! (N) RVO đang diễn ra và không cần thực hiện thêm hành động nào trong bước này!
Quay lại câu hỏi của bạn:
"Cách tìm các hoạt động sao chép giả của C ++"
Như đã thấy ở trên, bạn có thể giới thiệu một lớp proxy ở giữa cho mục đích gỡ lỗi.
Làm cho copy-ctor private có thể không hoạt động trong nhiều trường hợp, vì bạn có thể có một số bản sao mong muốn và một số bản sao bị ẩn. Như trên, chỉ có mã ví dụ 4 sẽ hoạt động với một copy-ctor riêng! Và tôi không thể trả lời câu hỏi, nếu ví dụ 4 là câu hỏi nhanh nhất, vì chúng ta lấp đầy hòa bình bằng hòa bình.
Xin lỗi rằng tôi không thể cung cấp một giải pháp chung cho việc tìm các bản sao "không mong muốn" ở đây. Ngay cả khi bạn đào mã của mình cho các cuộc gọi memcpy
, bạn sẽ không tìm thấy tất cả vì nó cũng memcpy
sẽ được tối ưu hóa và bạn sẽ thấy trực tiếp một số hướng dẫn trình biên dịch thực hiện công việc mà không cần gọi đến memcpy
chức năng thư viện của bạn .
Gợi ý của tôi là không tập trung vào một vấn đề nhỏ như vậy. Nếu bạn có vấn đề về hiệu suất thực sự, hãy lấy một hồ sơ và đo lường. Có rất nhiều kẻ giết người hiệu suất tiềm năng, rằng đầu tư nhiều thời gian vào memcpy
việc sử dụng giả có vẻ không phải là một ý tưởng đáng giá.
std::vector
bằng bất kỳ phương tiện nào không phải là mục đích của nó . Ví dụ của bạn hiển thị một bản sao rõ ràng và nó chỉ là tự nhiên và cách tiếp cận đúng, (một lần nữa imho) để áp dụngstd::move
chức năng như bạn tự đề xuất nếu một bản sao không phải là điều bạn muốn. Lưu ý rằng một số trình biên dịch có thể bỏ qua việc sao chép nếu cờ tối ưu hóa được bật và vectơ không thay đổi.