Я вважаю, у вас правильне спостереження, але неправильне тлумачення!
Копія не відбудеться, повертаючи значення, оскільки кожен звичайний розумний компілятор буде використовувати (N) RVO в цьому випадку. З C ++ 17 це обов'язково, тому ви не можете побачити жодну копію, повертаючи локальний згенерований вектор з функції.
Гаразд, давайте трохи пограємо з std::vectorтим, що буде під час будівництва або заповнивши його поетапно.
Перш за все, давайте може генерувати тип даних, який робить кожну копію чи рух видимим, як цей:
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; }
};
А тепер давайте розпочнемо кілька експериментів:
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; }
}
Що ми можемо спостерігати:
Приклад 1) Ми створюємо вектор зі списку ініціалізатора, і, можливо, ми очікуємо, що ми побачимо 4 рази побудувати і 4 ходи. Але ми отримуємо 4 екземпляри! Це звучить трохи загадково, але причина - реалізація списку ініціалізаторів! Просто не дозволяється переміщатися зі списку, оскільки ітератор зі списку - const T*це неможливе переміщення елементів із нього. Детальну відповідь на цю тему можна знайти тут: Initilizer_list та семантика переміщення
Приклад 2) У цьому випадку ми отримуємо початкову конструкцію та 4 копії значення. Це не є особливим, і ми можемо очікувати.
Приклад 3) Також тут ми будуємо конструкцію та деякі кроки, як очікувалося. З моєю реалізацією stl вектор щоразу зростає на коефіцієнт 2. Отже, ми бачимо першу конструкцію, іншу і тому, що вектор змінює розмір від 1 до 2, ми бачимо переміщення першого елемента. Додаючи 3, ми бачимо розмір від 2 до 4, який потребує переміщення перших двох елементів. Все як очікувалося!
Приклад 4) Тепер ми резервуємо простір і заповнюємо пізніше. Тепер у нас немає жодної копії і жодного руху!
У всіх випадках ми не бачимо жодного переміщення чи копіювання, повертаючи вектор назад абоненту! (N) RVO відбувається, і подальших дій на цьому кроці не потрібно!
Назад до свого питання:
"Як знайти C ++ операції копіювання копій"
Як було показано вище, ви можете ввести клас проксі між ними для налагодження.
Зробити приватний копіювальний механізм може не працювати у багатьох випадках, оскільки у вас можуть бути потрібні копії та деякі приховані. Як вище, тільки код, наприклад, 4, буде працювати з приватним копіратором! І я не можу відповісти на запитання, якщо приклад 4 - найшвидший, оскільки миром ми наповнюємо мир.
Вибачте, що тут не можу запропонувати загального рішення для пошуку "небажаних" копій. Навіть якщо ви копаєте свій код для дзвінків memcpy, ви не знайдете все, що також memcpyбуде оптимізовано, і ви побачите безпосередньо деякі інструкції асемблера, які виконують роботу, без виклику вашої бібліотечної memcpyфункції.
Мій натяк - не зосереджуватись на такій незначній проблемі. Якщо у вас є реальні проблеми з продуктивністю, візьміть профайлер і заміряйте. Існує так багато потенційних вбивць продуктивності, що інвестувати багато часу на помилкове memcpyвикористання здається не такою гідною ідеєю.
std::vectorбудь-якими способами не є тим, яким він має намір бути . У вашому прикладі показана явна копія, і застосуватиstd::moveфункцію так, як ви самі пропонуєте, є природним і правильним підходом, (знову ж таки імхо), якщо копія - це не те, що потрібно. Зауважте, що деякі компілятори можуть пропустити копіювання, якщо включені прапори оптимізації, а вектор не змінюється.