Является ли передача по значению намного быстрее?
Я слышал, что вы всегда должны предпочитать "проход по значению" в C++11 из-за введения семантики перемещения. Я хотел посмотреть, что же такое шумиха, и создал тестовый пример. Сначала мой класс:
struct MyClass {
MyClass() { }
MyClass(const MyClass&) { std::cout << "Copy construct" << std::endl; }
MyClass(MyClass&&) { std::cout << "Move construct" << std::endl; }
~MyClass() { }
};
И испытательный жгут:
class Test
{
public:
void pass_by_lvalue_ref(const MyClass& myClass)
{
_MyClass.push_back(myClass);
}
void pass_by_rvalue_ref(MyClass&& myClass)
{
_MyClass.push_back(std::move(myClass));
}
void pass_by_value(MyClass myClass)
{
_MyClass.push_back(std::move(myClass));
}
private:
std::vector<MyClass> _MyClass;
};
Предположительно, pass_by_value
должен выиграть pass_by_lvalue_ref
а также pass_by_rvalue_ref
(вместе, а не отдельно).
int main()
{
MyClass myClass;
Test Test;
std::cout << "--lvalue_ref--\n";
Test.pass_by_lvalue_ref(myClass);
std::cout << "--rvalue_ref--\n";
Test.pass_by_rvalue_ref(MyClass{});
std::cout << "--value - lvalue--\n";
Test.pass_by_value(myClass);
std::cout << "--value - rvalue--\n";
Test.pass_by_value(MyClass{});
}
Это мой вывод на GCC 4.9.2 с -O2
:
--lvalue_ref--
Copy construct
--rvalue_ref--
Move construct
Copy construct
--value - lvalue--
Copy construct
Move construct
Copy construct
Copy construct
--value - rvalue--
Move construct
Как видите, неpass_by_value
Для функций требуется всего 2 конструкции копирования и 1 конструкция перемещения. pass_by_value
функция требует в общей сложности 3 конструкции копирования и 2 конструкции перемещения. Похоже, что, как и ожидалось, объект все равно будет скопирован, так почему все говорят, что они передаются по значению?
2 ответа
Во-первых, ваши отчеты полностью ошибочны. Каждая из ваших функций возвращается к одному и тому же вектору. Когда этот вектор исчерпает емкость (которая зависит от того, сколько элементов вы уже вставили), он инициирует перераспределение, которое потребует больше ходов и / или копий, чем вставка, которая не инициирует выделение,
Во-вторых, std::vector::push_back
имеет сильное исключение гарантия безопасности. Поэтому, если ваш конструктор перемещения не исключение, он не будет его использовать (если только класс не подлежит копированию). Вместо этого он будет использовать конструктор копирования.
В третьих,
Я слышал, что вы всегда должны предпочитать "проход по значению" в C++11 из-за введения семантики перемещения.
Я почти уверен, что вы не слышали об этом ни от одного авторитетного источника. Или на самом деле неуместно перефразировать то, что на самом деле было сказано. Но у меня нет источника цитаты. Обычно рекомендуется, что если вы все равно собираетесь копировать аргументы в своей функции, не делайте этого. Просто сделайте это в списке параметров (через передачу по значению). Это позволит вашей функции перемещать аргументы r-значения прямо к месту назначения. Когда вы передаете l-значения, они будут скопированы, но вы все равно собираетесь это сделать.
Если вы собираетесь сделать внутреннюю копию, то передача по значению сделает ровно на одну конструкцию перемещения больше, чем пара перегрузок (передача по rvalue ref)+(передача по const lvalue ref).
Если конструкция перемещения дешевая, это небольшое количество времени выполнения в обмен на меньшее время компиляции и затраты на обслуживание кода.
Идиома: "Хотите скорости? В любом случае, делать копию? Передавать по значению, а не по постоянной ссылке". в действительности.
Наконец, ваш эталонный тест имеет недостатки, так как вы не смогли зарезервировать (достаточно) до того, как ваш откат отступит Перераспределение может вызвать дополнительные операции. Да, и сделайте ваш конструктор перемещения без исключения, поскольку соответствующие библиотеки предпочтут копию перемещению, если перемещение может генерировать во многих ситуациях.