Вопросы о том, как работает передача аргументов в C++

Итак, я довольно опытный программист на C, которому приходится много программировать на C++. В языке есть некоторые тонкости, в которых я никогда не чувствовал себя уверенным. Например, лучшие методы передачи аргументов.

Предположим, например, что у меня есть класс черного ящика с именем Object (у него может быть много переменных-членов, насколько нам известно) и функция fn, которая принимает вектор экземпляров Object в качестве аргумента. Мне кажется, что есть четыре основных способа прохождения этого:

void fn(vector<Object> vec);
void fn(vector<Object*> vec);
void fn(vector<Object> *vec);
void fn(vector<Object> &vec);

И, конечно же, мы могли бы также взять некоторую комбинацию этих функций.

Я хочу убедиться, что у меня есть это прямо:

Метод 1 скопирует векторный класс, включая копию каждого экземпляра объекта в векторе. Это потенциально может быть огромной перегрузкой и, следовательно, плохо.

(в этом я не уверен) Метод 2 скопирует все переменные метода vec, но скопирует только адреса каждого из экземпляров объекта. Я недостаточно знаю о том, что содержится в векторном классе, чтобы понять, целесообразно это или нет.

Методы 3 и 4 довольно просты и похожи друг на друга и требуют минимальных накладных расходов.

Все это правильно? и какой метод предпочтительнее, учитывая, что мы ничего не знаем о классе Object?


person sicklybeans    schedule 06.08.2013    source источник
comment
Предпочтительный метод зависит от того, что вы хотите сделать, но если вы не хотите изменять вектор и не хотите копировать, то обычным подходом будет передача ссылки const: void fn(const vecctor<Object>& vec);.   -  person juanchopanza    schedule 06.08.2013
comment
возможный дубликат Как правильно передать параметры? - за исключением того, что ваш второй вариант не покрыто там, потому что это не совсем связано   -  person stijn    schedule 06.08.2013


Ответы (4)


Предпочтительным способом передачи аргумента будет
void fn(const vector<Object> &vec);
При условии, конечно, что вы его не изменяете. Использование указателей, как в случае 2 и 3, обычно не является хорошей практикой в ​​C++.
Теперь версия 1:
void fn(vector<Object> vec);
Обычно считается плохой, потому что она создает копию, но иногда это необходимо. Имейте в виду, что иногда компилятор может оптимизировать копию, используя исключение копии в случае временные и если копия не изменена (но в этом случае вы должны использовать константную ссылку)

person aaronman    schedule 06.08.2013
comment
Но версия 3 может быть предпочтительнее, если вам нужна копия внутри функции, поскольку она допускает некоторую возможность исключения копии. - person juanchopanza; 06.08.2013
comment
@juanchopanza разве я не это говорю, я закончил это необходимым - person aaronman; 06.08.2013
comment
Почти. Не всегда делает копию. - person juanchopanza; 06.08.2013
comment
@juanchopanza, если вы измените его, разве он не всегда будет делать копию? - person aaronman; 06.08.2013
comment
Я имею в виду, что если вы передаете временное значение, копия может быть исключена. - person juanchopanza; 06.08.2013
comment
@juanchopanza, разве это не должно быть решено путем перехода на С++ 11 - person aaronman; 06.08.2013
comment
Это будет обрабатываться копированием elision как до С++ 11, так и в С++ 11. Если удаление копии не происходит, тогда ее можно переместить (очевидно, в C++11). - person juanchopanza; 06.08.2013
comment
@juanchopanza хорошо, я не слишком хорошо знаком с CE, но я упомянул об этом сейчас - person aaronman; 06.08.2013
comment
stackoverflow .com/questions/7592630/ - person stijn; 06.08.2013
comment
@stijn это в основном поддерживает то, что я говорю, const ref является лучшим, на данный момент я чувствую, что есть так много способов передать переменные в С++, что вам действительно нужно просто решить, какие соглашения вы хотите соблюдать в своей кодовой базе - person aaronman; 06.08.2013
comment
вы должны прочитать снова. const ref не всегда «лучший». Только когда вы только планируете проверить аргумент. Если вы собираетесь изменить его, перейдите по ссылке. Но вы не должны обычно считать передачу по avlue чем-то плохим. Вместо этого, если вы все равно собираетесь его копировать, передайте аргумент по значению (и в конечном итоге откажитесь от него). Например, см. здесь: stackoverflow.com/questions/7592630/ - person stijn; 07.08.2013
comment
@stijn, когда я сказал всегда самое лучшее, я действительно имел в виду, когда вы не изменяете, вы должны обязательно это сделать, что касается остальной части вашего аргумента, похоже, вы в основном согласны со мной, я забыл поговорить о переезде, потому что вопрос не был помечен c++11 - person aaronman; 07.08.2013

Все, что вы говорите, правильно.

Если вы все равно собираетесь копировать вектор внутри функции или если вы хотите изменить vec внутри fn, но не изменять исходную копию, используйте #1. Затем вы можете сохранить себе копию внутри и просто позволить компилятору (возможно) сделать копию за вас.

Если вы хотите изменить исходный вектор (извне fn), используйте #4.

Если вы вообще не хотите изменять вектор, используйте #4 с const:

void fn(const vector<Object> &vec);

Не используйте № 2, потому что вам будет очень трудно поместить указатель в вектор. Память должна быть newd и deleted, и она будет подвержена ошибкам. [Обязательное примечание shared_ptr].

Ваш № 3 не является идиоматическим С++. Это нормально, но в этом случае идиоматический C++ избегает указателей в пользу ссылок.

person JoshG79    schedule 06.08.2013
comment
Если функция собирается сохранить дескриптор вектора где-то, где он будет использоваться позже (после возврата из функции), используйте #3. Idiomatic C++ использует указатели (или интеллектуальные указатели), когда необходимо решить проблемы, связанные с продолжительностью жизни. - person Ben Voigt; 06.08.2013

Это все правильно. Метод 2 действительно сделает копию vec, и, поскольку его элементы являются указателями на Object, он будет копировать только эти указатели, а не фактические объекты. Это не сильно отличается от передачи массива указателей на Object.

Как правило, наличие векторов указателей не одобряется. Когда вектор выходит за пределы области видимости, эти указатели не удаляются автоматически. Вы должны были бы быть уверены, чтобы удалить их самостоятельно. И если у вас все еще будет утечка памяти, если ваш код выдает исключение в любой момент до этого.

Если объекты маленькие, вы должны использовать вектор объектов, а не указатели. Если объекты большие, вы должны использовать вектор интеллектуальных указателей.

person Dima    schedule 06.08.2013

В основном вы поняли это правильно, и другие дали хороший ответ и замечания. Я просто хочу добавить два комментария:

1) Если у вас есть

std::vector<Object> v;

тогда, очевидно, вы не можете вызвать fn(v) для перегрузки void fn(vector<Object*> vec);.

2) Вы сказали:

«Я недостаточно знаю, что содержится в векторном классе»…

Наиболее популярные реализации std::vector имеют только три указателя в качестве элементов данных, и стоимость копирования этих трех указателей очень мала. Однако это не то, что происходит, когда вы копируете std::vector (это было бы, если бы его конструктор копирования был сгенерирован компилятором). На самом деле std::vector имеет конструктор копирования, который (как вы сказали) копирует все элементы исходного std::vector. Это также включает в себя выделение памяти и может быть очень дорогостоящей операцией.

person Cassio Neri    schedule 06.08.2013