Skip to content

Latest commit

 

History

History
263 lines (239 loc) · 34.8 KB

File metadata and controls

263 lines (239 loc) · 34.8 KB


Rvalue, move

Передаваемые значения

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 копирований ыыы

Return value optimization (RVO)

И еще

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, будет ли точно оно возвращено

Ну погнали rvalue

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

Типичные ошибки

  1. Написав так, можно подавить NRVO

    string foo() {
    	string res = "hello";
    	res += "world";
    	return move(res);
    }
    
  2. T не поддерживает rvalue, тогда будет вызван просто конструктор копирования

    	T a;
    	T b = std::move(a);
    

    move не указ, а подсказка

  3. 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)
    Поэтому просто статически проверяем является ли move noexcept, иначе не используем.

    enable_if<noexcept(a + b)>::type - статическая проверка noexcept

Можно писать в noexcept любое логическое выражение

bool foo(T) noexcept(enable_if<noexcept(T)>::type);

Для работы move при создании объекта (н-р добавление в вектор) требуется noexcept конструктора. Еще это важно для hashCode()(иначе проблемы с рехэшэм и надо хранить хэш рядом с объектом, а спрашивать его только при вставке)

Еще применение rvalue

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) ...);
}