Известен также, как Koenig lookup.
namespace X
{
struct A { ... };
std::ostream& operator<<(
std::ostream& out, const A& value) { ... }
void foo(const A& value) { ... }
}
X::A a;
std::cout << a;
foo(a);
Компилятор ищет функцию в текущем пространстве имен и если не находит, то в пространствах имен аргументов. Если находит подходящую функцию в двух местах, то возникает ошибка.
struct A
{
X x;
Y y;
// Конструктор
A()
: x(X())
, y(Y())
{}
// Деструктор
~A()
{}
// Копирующий конструктор
// A a1;
// A a2 = a1;
A(const A& copied)
: x(copied.x)
, y(copied.y)
{}
// Оператор копирования
// A a1;
// A a2;
// a2 = a1;
A& operator=(const A& copied)
{
x = copied.x;
y = copied.y;
return *this;
}
// Перемещающий конструктор
// A a1;
// A a2 = std::move(a1);
A(A&& moved)
: x(std::move(moved.x))
, y(std::move(moved.y))
{}
// Оператор перемещения
// A a1;
// A a2;
// a2 = std::move(a1);
A& operator=(A&& moved)
{
x = std::move(moved.x);
y = std::move(moved.y);
return *this;
}
};
Если явно объявить один из следующих методов:
- деструктор
- конструктор копирования
- оператор копирования
(после С++11, еще два)
- конструктор перемещения
- оператор перемещения
То компилятор не будет генерировать остальные автоматически, поэтому если они вам нужны, вы должны реализовать их самостоятельно.
До стандарта С++11 было два типа значений:
- lvalue
- rvalue
"Объект - это некоторая именованная область памяти; lvalue - это выражение, обозначающее объект. Термин "lvalue" произошел от записи присваивания Е1 = Е2, в которой левый (left - левый(англ.), отсюда буква l, value - значение) операнд Е1 должен быть выражением lvalue."
Керниган и Ритчи
int a = 1;
int b = 2;
int c = (a + b);
int foo() { return 3; }
int d = foo();
1 = a; // left operand must be l-value
foo() = 2; // left operand must be l-value
(a + b) = 3; // left operand must be l-value
- Ссылается на объект - lvalue
- Если можно взять адрес - lvalue
- Все что не lvalue, то rvalue
int a = 3;
a; // lvalue
int& b = a;
b; // lvalue, ссылается на a
int* c = &a;
*c; // lvalue, ссылается на a
void foo(int val)
{
val; // lvalue
}
void foo(int& val)
{
val; // lvalue, ссылается на val
}
int& bar() { return a; }
bar(); // lvalue, ссылается на a
3; // rvalue
(a + b); // rvalue
int bar() { return 1; }
bar(); // rvalue
Ссылка на lvalue.
int a = 3;
int& b = a;
int& a = 3; // ошибка
const int& a = 3; // ок
a; // const lvalue
Объект жив до тех пор, пока жива ссылающаяся на него константная ссылка.
#include <iostream>
int x = 0;
int val() { return 0; }
int& ref() { return x; }
void test(int&)
{
std::cout << "lvalue\n";
}
void test(int&&)
{
std::cout << "rvalue\n";
}
int main()
{
test(0); // rvalue
test(x); // lvalue
test(val()); // rvalue
test(ref()); // lvalue
test(std::move(x)); // rvalue
return 0;
}
std::move
приводит lvalue к rvalue
Семантика: в результате копирования должна появится точная копия объекта.
int x = 3;
int y = x;
// x == y
String a;
String b = a;
String c;
c = a;
// a == b == c
class String
{
size_t size_;
char* data_;
public:
~String()
{
delete[] data_;
}
// String b1;
// String b2 = b1;
String(const String& copied)
: data_(new char[copied.size_])
, size_(copied.size_)
{
std::copy(copied.data_, copied.data_ + size_, data_);
}
// String b1;
// String b2;
// b2 = b1;
String& operator=(const String& copied)
{
// Плохо
delete[] data_;
data_ = new char[copied.size_];
size_ = copied.size_;
std::copy(copied.data_, copied.data_ + size_, data_);
return *this;
}
};
String b1;
b1 = b1;
std::vector<String> words;
...
words[to] = words[from];
Проверяйте на присваивание самому себе.
String& operator=(const String& copied)
{
if (this == &copied)
return *this;
// Плохо
delete[] data_;
data_ = new char[copied.size_];
size_ = copied.size_;
std::copy(copied.data_, copied.data_ + size_, data_);
return *this;
}
Финальный вариант:
String& operator=(const String& copied)
{
if (this == &copied)
return *this;
char* ptr = new char[copied.size_];
delete[] data_;
data_ = ptr;
size_ = copied.size_;
std::copy(copied.data_, copied.data_ + size_, data_);
return *this;
}
Подумайте, а стоит ли писать конструктор/оператор копирования самостоятельно?
struct A
{
A() {}
A(const A& a) {}
virtual A& operator=(const A& copied)
{ return *this; }
};
class B
: public A
{
public:
B() {}
B(B& b)
: A(b)
{
}
A& operator=(const A& copied) override
{
A::operator=(copied);
return *this;
}
};
void foo(A a)
{
// Срезанный до А объект
}
B a;
foo(a);
void send(std::vector<char> data)
{
...
}
void print(const std::vector<char>& data)
Используйте передачу по ссылке!
До С++11:
class Noncopyable
{
Noncopyable(const Noncopyable&);
Noncopyable& operator=(const Noncopyable&);
};
class Buffer
: private Noncopyable
{
};
boost::noncopyable
устроен именно так.
С++11:
class Buffer
{
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
};
class Buffer
{
public:
Buffer(const Buffer&) = default;
Buffer& operator=(const Buffer&) = default;
};
Семантика: в результате перемещения в объекте, куда происходит перемещение, должна появиться точная копия перемещаемого объекта, оригинальный объект после этого остается в неопределенном, но корректном состоянии.
class unique_ptr
{
T* data_;
};
class Buffer
{
char* data_;
size_t size_;
};
Выражение, чьё вычисление определяет сущность объекта.
Выражение, чьё вычисление инициализирует объект или вычисляет значение операнда оператора, с соответствии с контекстом использования.
Это glvalue, которое обозначает объект, чьи ресурсы могут быть повторно использованы (обычно потому, что они находятся около конца своего времени жизни).
Это glvalue, которое не является xvalue.
Это prvalue или xvalue.
Выражение является lvalue, если ссылается на объект уже имеющий имя доступное вне выражения.
int a = 3;
a; // lvalue
int& b = a;
b; // lvalue
int* c = &a;
*c; // lvalue
int& foo() { return a; }
foo(); // lvalue
- Результат вызова функции возвращающей rvalue-ссылку
int&& foo() { return 3; }
foo(); // xvalue
- Явное приведение к rvalue
static_cast<int&&>(5); // xvalue
std::move(5); // эквивалентно static_cast<int&&>
- Результат доступа к нестатическому члену, объекта xvalue значения
struct A
{
int i;
};
A&& foo() { return A(); }
foo().i; // xvalue
Не принадлежит ни к lvalue, ни к xvalue.
int foo() { return 3; }
foo(); // prvalue
Все что принадлежит к xvalue или prvalue.
Все что принадлежит к xvalue или lrvalue.
- Можно взять адрес - lvalue
- Ссылается на lvalue (T&, const T&) - lvalue
- Иначе rvalue
Как правило rvalue соответствует временным объектам, например, возвращаемым из функций или создаваемых в результате неявных приведений типов. Также это большинство литералов.
Есть имя | Может быть перемещено | Тип |
---|---|---|
да | нет | glvalue, lvalue |
да | да | rvalue, xvalue, glvalue |
нет | да | rvalue, prvalue |
void foo(int) {}
void foo(int&) {}
void foo(int&&) {}
void foo(int) {} // <-- этот?
void foo(int&) {} // // <-- или этот?
void foo(int&&) {}
int x = 1;
foo(x); // lvalue
int x = 1;
int& y = x;
foo(y); // lvalue
void foo(int) {} // <-- этот?
void foo(int&) {} // <-- или этот?
void foo(int&&) {}
foo(1); // rvalue
void foo(int) {} // <-- этот?
void foo(int&) {}
void foo(int&&) {} // <-- или этот?
int bar() { return 1; }
foo(bar()); // rvalue
void foo(int) {} // <-- этот?
void foo(int&) {}
void foo(int&&) {} // <-- или этот?
foo(1 + 2); // rvalue
void foo(int) {} // <-- этот?
void foo(int&) {}
void foo(int&&) {} // <-- или этот?
class Buffer
{
size_t size_;
char* data_;
public:
~Buffer()
{
delete[] data_;
}
// Buffer b1;
// Buffer b2 = std::move(b1);
Buffer(Buffer&& moved)
: data_(moved.data_)
, size_(moved.size_)
{
moved.data_ = nullptr;
moved.size_ = 0;
}
// Buffer b1;
// Buffer b2;
// b2 = std::move(b1);
Buffer& operator=(Buffer&& moved)
{
if (this == &moved)
return *this;
delete[] data_;
data_ = moved.data_;
size_ = moved.size_;
moved.data_ = nullptr;
moved.size_ = 0;
return *this;
}
};
class Buffer
{
public:
Buffer(Buffer&&) = default;
Buffer& operator=(Buffer&&) = default;
};
class Buffer
{
public:
Buffer(Buffer&&) = delete;
Buffer& operator=(Buffer&&) = delete;
};
Задача: передать аргумент не создавая временных копий и не изменяя типа передаваемого аргумента.
void bar(T&) {}
void bar(T&&) {}
template <class T>
void foo(T x)
{
// копия
bar(x);
}
template <class T>
void foo(T& x)
{
// Может приводить к ошибкам
// компиляции, если x - rvalue:
// foo(T());
bar(x);
}
template <class T>
void foo(T&& x)
{
// ок, но пришлось написать перегрузку
bar(std::move(x));
}
Решение:
template <class T>
void foo(T&& x)
{
bar(std::forward<T>(x));
}
Позволяет сконструировать возвращаемый объект в точке вызова.
Server makeServer(uint16_t port)
{
Server server(port);
server.setup(...);
return server;
}
Server s = makeServer(8080);
Не мешайте компилятору:
Server&& makeServer(uint16_t port)
{
Server server(port);
server.setup(...);
return std::move(server); // так не надо
}
Оптимизация компилятора разрешающая избегать лишнего вызова копирующего конструктора.
struct A
{
explicit A(int) {}
A(const A&) {}
};
A y = A(5); // Копирующий конструктор вызван не будет
В копирующих конструкторах должна быть логика отвечающая только за копирование.
Написать класс для работы с большими целыми числами. Размер числа ограничен только размером памяти. Нужно поддержать семантику работы с обычным int:
BigInt a = 1;
BigInt b = a;
BigInt c = a + b + 2;
Реализовать оператор вывода в поток, сложение, вычитание, унарный минус, все операции сравнения.
std::vector и другие контейнеры использовать нельзя - управляйте памятью сами.
EOF