struct big_struct { std::array<int, 10> data; }
void foo(int ...) { }
int, int*, int& - передается через регистры/стек
А если передать структурку?
big_struct a;
f(a)
Надо скопировать, а в функцию передается ссылка
Как можно реализовать?
I
big_struct a;{ big_struct copy = a; f(copy); }
void f(big_struct&) { ... }
II
big_struct a; f(a);
void f(big_struct const& a) { big_struct copy = a; ... }
В с, с++ используется I вариант, так как в некоторых случаях можно не делать копию.
Структуры занимающие 1, 2 числа передаются обычно через регистры
По умолчанию результат записывается в специальный(ну не очень) регистр
Маленькие структуры возвращаются аналогично передаваемым
big_struct f();
void g() { big_struct a = f(); }
Транслируется в:
void f(void* result) { ... ctor_big_integer(result); //Конструктор }
void g() { char a_buf[sizeof(big_struct)]; f(a_buf); big_struct& a = (big_struct&)a_buf; }
Еще примерчик
void h() { f(g()); }
void g(void *); void h() { char tmp_buf[sizeof(big_struct)]; g(tmp_buf); big_struct& tmp = (big_struct&) tmp_buf; f(tmp); }
0 копирований ыыы
И еще
struct big_struct { big_struct(int ,int, int); };big_struct g() { ... return big_strust(1, 2, 3); }
big_struct f; g(f);
Наивный вариант
void f(void* result) {
char tmp[sizeof(big_struct)];
big_struct_ctor(tmp, 1, 2, 3);
big_struct_ctor(result, (big_struct&)tmp);
}
Нормальный
void f(void* result) {
big_struct_ctor(result, 1, 2, 3);
}
И ещее
NamedRVO
big_struct g() {
big_struct tmp(1,2,3);
...
tmp.f();
...
return tmp;
}
Зачем делать лишнее копирование?
big_struct g() {
big_struct_ctor(result, 1,2,3);
...
try {
(big_struct*)result -> f();
} catch (...) {
(big_struct*)result-> ~big_struct();
throw;
}
}
Неприменимо, если на момент создания tmp, будет ли точно оно возвращено
std::vector<std::string> v; std:string s; v.push_back(s);
И std::vector<file_descriptor> b;
В c++03 превращаем в
std::vector<file_descriptor*>
std::vector<boost::shared_ptr<file_descroptor>> v;
Если передать в функцию rvalue, то можно портить передаваемый элемент
С с++11 можно перегружать функции по lvalue И rvalue
int f(); int a = 5;int& b = a; //ok int&& c = a; //error int& d = f(); //error int&& e = f(); //ok
void push_back(T const&); void push_back(T&&);
Есть новый конструктор
string(string&&);
Именованная переменная всегда lvalue
void foo(T&&);
void push_back(T&& a) { foo(static_cast<T&&> (a)); }
Но получается слишком частая операция, поэтому есть встроенная функция
template <typename T>
T&& move(T& obj) {
return static_cast<T&&>(obj);
}
Не совсем корректно, еще remove_reference
Типичные ошибки
-
Написав так, можно подавить NRVO
string foo() { string res = "hello"; res += "world"; return move(res); } -
Tне поддерживаетrvalue, тогда будет вызван просто конструктор копированияT a; T b = std::move(a);moveне указ, а подсказка -
T* new_data = operator new(sizeof(T) * new_capacity); for (size_t i = 0; i != size; ++i) { new(&new_data[i]) T(std::move(old_data[i])); }Не exception-safety (н-р
Tне поддерживаетmove)
Поэтому просто статически проверяем является лиmovenoexcept, иначе не используем.enable_if<noexcept(a + b)>::type- статическая проверка noexcept
Можно писать в noexcept любое логическое выражение
bool foo(T) noexcept(enable_if<noexcept(T)>::type);
Для работы move при создании объекта (н-р добавление в вектор) требуется noexcept конструктора. Еще это важно для hashCode()(иначе проблемы с рехэшэм и надо хранить хэш рядом с объектом, а спрашивать его только при вставке)
Forwarding
void f(int); void f(char);
template<typename T> void g(T a) { ... f(a); ... }
Плохо, копирования, возможно снятие const и т.д.
Можно сделать 2 перегрузки - с const и без const. Но для 2 аргументов это уже 4 перегрузки. O(2^n) перегрузок - не оч.
Perfect forwarding
Когда сохраняем тип, константность и lvalue/rvalue.
Доопределили вывод:
template<typename T> void f(T&&);int a; int const b = 5; f(a); //T -> int& f(b); //T -> int const&
f(5); //T -> int
Еще работают следующие правила:
& & -> &
& && -> &
&& & -> &
&& && -> &&
perfect:
template<typename T> void g(T&& a) { ... f(static_cast<T&&>(a)); ... }
template<typename T> void g(T&& a) { ... f(forward<T>(a)); ... }
Наши move и forward полностью совпадают. В реале же они написаны немного по разному, так как у forward есть шаблонный параметр, иначе без него будет происходить move
Для многих аргументов
template<typename ... T>
void g(T&& ... a) {
f(forward<T>(a) ...);
}