Недавно у меня было следующее
struct data {
std::vector<int> V;
};
data get_vector(int n)
{
std::vector<int> V(n,0);
return {V};
}
Проблема с этим кодом заключается в том, что при создании структуры происходит копирование, и вместо этого нужно написать return return {std :: move (V)}
Есть ли линтер или анализатор кода, который обнаружил бы такие ложные операции копирования? Ни cppcheck, ни cpplint, ни clang-tidy не могут этого сделать.
РЕДАКТИРОВАТЬ: Несколько моментов, чтобы прояснить мой вопрос:
- Я знаю, что операция копирования произошла, потому что я использовал проводник компилятора, и он показывает вызов memcpy .
- Я мог бы определить, что операции копирования произошли, посмотрев на стандарт да. Но моя первоначальная неправильная идея заключалась в том, что компилятор оптимизировал бы эту копию. Я был неправ.
- Это (вероятно) не проблема компилятора, так как и clang, и gcc создают код, который создает memcpy .
- Memcpy может быть дешевым, но я не могу представить обстоятельства, когда копирование памяти и удаление оригинала дешевле, чем передача указателя с помощью std :: move .
- Добавление std :: move является элементарной операцией. Я полагаю, что анализатор кода сможет предложить это исправление.
c++
code-analysis
static-code-analysis
cppcheck
Матье Дутур Сикирич
источник
источник
std::vector
каким-либо образом не является тем, чем оно является . В вашем примере показана явная копия, и это вполне естественный и правильный подход (опять же imho) применитьstd::move
функцию, как вы предлагаете сами, если копия не то, что вам нужно. Обратите внимание, что некоторые компиляторы могут пропустить копирование, если флаги оптимизации включены, а вектор неизменен.Ответы:
Я верю, что у вас правильное наблюдение, но неверное толкование!
Копирование не произойдет при возврате значения, потому что каждый нормальный умный компилятор будет использовать (N) RVO в этом случае. В C ++ 17 это является обязательным, поэтому вы не можете увидеть ни одной копии, возвращая локально сгенерированный вектор из функции.
Хорошо, давайте немного поиграем с
std::vector
тем, что будет происходить во время строительства или путем его постепенного заполнения.Прежде всего, давайте создадим тип данных, который делает каждую копию или перемещение видимым, как этот:
А теперь давайте начнем некоторые эксперименты:
Что мы можем наблюдать:
Пример 1) Мы создаем вектор из списка инициализаторов и, возможно, мы ожидаем, что мы увидим 4 раза построение и 4 хода. Но мы получаем 4 копии! Это звучит немного загадочно, но причина в реализации списка инициализаторов! Просто нельзя перемещаться из списка, так как итератор из списка является
const T*
что делает невозможным перемещение элементов из него. Подробный ответ на эту тему можно найти здесь: initializer_list и семантика перемещенияПример 2) В этом случае мы получаем начальную конструкцию и 4 копии значения. В этом нет ничего особенного, и это то, что мы можем ожидать.
Пример 3) Также здесь, мы строим и некоторые шаги, как ожидалось. С моей реализацией stl вектор растет в 2 раза каждый раз. Таким образом, мы видим первую конструкцию, другую, и поскольку вектор изменяется с 1 на 2, мы видим движение первого элемента. Добавляя 3, мы видим изменение размера от 2 до 4, которое требует перемещения первых двух элементов. Все как и ожидалось!
Пример 4) Теперь мы оставляем за собой место и заполняем позже. Теперь у нас нет копии и больше нет движения!
Во всех случаях мы не видим ни перемещения, ни копирования, вообще возвращая вектор вызывающей стороне! (N) RVO происходит, и на этом этапе никаких дальнейших действий не требуется!
Вернуться к вашему вопросу:
Как видно выше, вы можете ввести прокси-класс между ними для целей отладки.
Приватное копирование ctor может не сработать во многих случаях, так как у вас могут быть некоторые требуемые копии и некоторые скрытые. Как и выше, только код для примера 4 будет работать с частным копи-ctor! И я не могу ответить на вопрос, является ли пример 4 самым быстрым, поскольку мы наполняем мир миром.
Извините, что я не могу предложить общее решение для поиска «нежелательных» копий здесь. Даже если вы копаете свой код для вызовов
memcpy
, вы не найдете все, что такжеmemcpy
будет оптимизировано, и вы увидите, как некоторые инструкции на ассемблере выполняют свою работу без вызова вашей библиотечнойmemcpy
функции.Мой намек не в том, чтобы сосредоточиться на такой незначительной проблеме. Если у вас есть реальные проблемы с производительностью, возьмите профилировщик и измерьте. Потенциальных убийц производительности так много, что тратить много времени на ложное
memcpy
использование кажется не очень полезной идеей.источник
Поместили ли вы свое полное приложение в проводник компилятора и включили ли вы оптимизации? Если нет, то то, что вы видели в проводнике компилятора, может или не может быть тем, что происходит с вашим приложением.
Одна проблема с кодом, который вы опубликовали, заключается в том, что вы сначала создаете
std::vector
, а затем копируете его в экземплярdata
. Было бы лучше инициализироватьdata
с вектором:Кроме того, если вы просто дадите обозревателю компилятора определение
data
иget_vector()
, и ничего больше, он будет ожидать худшего. Если вы фактически дадите ему некоторый исходный код, который используетget_vector()
, то посмотрите, какая сборка сгенерирована для этого исходного кода. Посмотрите этот пример, чтобы узнать, что приведенная выше модификация плюс фактическое использование плюс оптимизация компилятора могут привести к созданию компилятором.источник