Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Лекция №3. «Черные ящики».

Baramiya Denis
September 19, 2018

Лекция №3. «Черные ящики».

1. Виды конструкторов.
2. Конструкторы копирования и перемещения.
3. Перегрузка оператора присваивания.
4. Присваивание-копирование и присваивание-перемещение.
5. Операции, генерируемые компилятором по умолчанию.
6. Арифметические и побитовые операторы. Операторы (составного) присваивания, сравнения, логические, с указателями и др.
7. Оператор преобразования типа. Создание временного объекта с вызовом конструктора с одним аргументом.
8. Явные и неявные преобразования типов. Ключевое слово explicit.
9. Глобальные operator new и operator delete.
10. Placement new.
11. Задание operator new и operator delete для конкретного класса.
12. Удаление версий операторов, сгенерированных по умолчанию.
13. Алгоритм поиска оператора.
14. Операторы, которые нельзя перегрузить.

Baramiya Denis

September 19, 2018
Tweet

More Decks by Baramiya Denis

Other Decks in Education

Transcript

  1. struct Point { double x, y, z; Point() = default;

    Point(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {} }; Point a(10, 20, 30); Point b(a); Point c(Point(1, 2, 3)); Point d; Конструктор, определенный 
 пользователем Конструктор копирования
 (неявный) Конструктор по умолчанию Конструктор перемещения
 (неявный)
  2. КОНСТРУКТОР КОПИРОВАНИЯ Point zeroPoint() { return Point(0, 0, 0); }

    1. Объект является возвращаемым значением
  3. ВАРИАНТЫ X(const X& copy_from_me); X(X& copy_from_me); X(volatile X& copy_from_me); X(const

    volatile X& copy_from_me); X(X& copy_from_me, int = 0); X(const X& copy_from_me, double = 1.0, int = 42);
  4. struct Point { double x, y, z; Point(double _x, double

    _y, double _z) : x(_x), y(_y), z(_z) {} // Не надо такое писать! Point(const Point &other) : x(other.x), y(other.y), z(other.z) {} }; Такой конструктор можно не писать, 
 он будет сгенерирован автоматически 
 (копирование всех полей)
  5. struct Buffer { char *buf; size_t size; Buffer(size_t sz) :

    buf(new char[sz]), size(sz) {} ~Buffer() { delete buf; } }; Как здесь будет работать КК по умолчанию?
  6. Buffer::Buffer(const Buffer &other) : size(other.size) { buf = new char[size];

    if (buf) memcpy(buf, other.buf, size); } Иначе: будет очень-очень плохо!
  7. Buffer fillBuffer() { Buffer b(1024); memset(b.buf, 0, b.size); return b;

    } Buffer mybuf = fillBuffer(); Насколько хорошо и быстро будет
 это работать?
  8. 1. Конструктор A() внутри fillBuffer(). 2. Конструктор копирования во временное

    значение при return. 3. Конструктор копирования в mybuf. В наихудшем случае:
  9. КОНСТРУКТОР ПЕРЕМЕЩЕНИЯ struct Buffer { char *buf; size_t size; Buffer(size_t

    sz); Buffer(const Buffer &other); Buffer(Buffer &&other) { buf = other.buf; size = other.size; other.buf = nullptr; other.size = 0; } ~Buffer(); };
  10. // Классический swap template<class T> void swap(T& a, T& b)

    { T tmp {a}; // две копии a a = b; // две копии b b = tmp; // две копии tmp (старое a) }
  11. // Почти идеальный swap template<class T> void swap(T& a, T&

    b) { T tmp {static_cast<T&&>(a)}; // конструктор может затереть a a = static_cast<T&&>(b); // присваивание может затереть b b = static_cast<T&&>(tmp); // присваивание может затереть tmp } // То же самое, с использованием std::move template<class T> void swap(T& a, T& b) { T tmp {std::move(a)}; // перемещаем из a a = std::move(b); // перемещаем из b b = std::move(tmp); // перемещаем из tmp }
  12. ПЕРЕГРУЗКА ОПЕРАТОРА ПРИСВАИВАНИЯ • Существует реализация по умолчанию: копирование всех

    полей. • Если в классе присутствуют указатели, реализация по умолчанию приносит проблемы.
  13. struct Buffer { char *buf; size_t size; Buffer(size_t sz) :

    buf(new char[sz]), size(sz) {} ~Buffer() { delete buf; } }; Buffer b1(100), b2(200); b1 = b2; // ОЙ
  14. Buffer &Buffer::operator=(const Buffer &other) { delete buf; buf = new

    char[size = other.size]; if (buf) memcpy(buf, other.buf, size); return *this; } Buffer buf(100), buf2(200); buf = buf; // ОЙ
  15. Buffer &Buffer::operator=(const Buffer &other) { if (this != &other) {

    delete buf; buf = new char[size = other.size]; if (buf) memcpy(buf, other.buf, size); } return *this; } Защита от самоприсваивания
  16. Buffer &Buffer::operator=(Buffer &&other) { buf = other.buf; buf.size = other.size;

    other.buf = nullptr; other.size = 0; return *this; } Присваивание с перемещением
  17. ОПЕРАЦИИ ПО УМОЛЧАНИЮ • X() • X(const X &) •

    X& operator=(const X &) • X(X &&) • X& operator=(X &&) • ~X()
  18. struct S { string a; int b; }; S f(S

    arg) { S s0 {}; // конструктор по умолчанию: {"",0} S s1 {s0}; // конструктор копирования s1 = arg; // копирующее присваивание return s1; // конструктор перемещения }
  19. • Если явно определён любой конструктор, конструктор по умолчанию X()

    не генерируется. • Перемещающие операции генерируются только для классов, в которых нет явно объявленных перемещающих операций, копирующих операций и деструктора. • Копирующие операции генерируются только для классов, в которых нет явно объявленных перемещающих операций и соответствующей операции копирования. • Генерация копирующих операций в классах с явно объявленным деструктором является устаревшей и может быть отменена в будущем. Но:
  20. • Можно перегрузить поведение почти всех операторов, существующих в языке.

    • Нельзя создать новые операторы. • Нельзя повлиять на приоритет, арность или ассоциативность операторов.
  21. A a; SomeType b; // a + b class A

    { // ... return_type operator+(SomeType b); }; // b + a ? Оператор-член класса
  22. Оператор-глобальная функция class A { // ... friend return_type operator+(const

    A &, SomeType); friend return_type operator+(SomeType, const A &); }; inline return_type operator+(const A &a, SomeType b) { // ... } inline return_type operator+(SomeType b, const A &a) { // ... }
  23. АРИФМЕТИКА • + - * / % • Можно использовать

    оба варианта функций. • operator- бывает двух видов!
  24. ПОБИТОВЫЕ ОПЕРАЦИИ • ^ | & ~ << >> •

    Приоритет ниже, чем у арифметики:
 a ^ n + b воспримется как a ^ (n + b). • Можно использовать оба варианта функций. • << и >> используются стандартной библиотекой для ввода/вывода.
  25. ostream& operator<<(ostream& out, const Vector2D& vec) { // вывод out

    << "(" << vec.x() << ", " << vec.y() << ")"; return out; } istream& operator>>(istream& in, Vector2D& vec) { // ввод double x, y; // пропускаем открывающую скобку in.ignore(1); // читаем x in >> x; vec.set_x(x); // пропускаем разделитель in.ignore(2); // читаем y in >> y; vec.set_y(y); // пропускаем закрывающую скобку in.ignore(1); return in; }
  26. ОПЕРАТОР ПРИСВАИВАНИЯ • Только метод класса! • Существует реализация по

    умолчанию: копирование всех полей. • Если в классе присутствуют указатели, реализация по умолчанию приносит проблемы.
  27. ОПЕРАТОРЫ СРАВНЕНИЯ • == != < <= > >= •

    Можно использовать оба варианта функций. • Чтобы не мучаться: • #include <utility> • using namespace std::rel_ops; • Определить operator== и operator< • Остальные операторы обеспечит STL
  28. ЛОГИЧЕСКИЕ ОПЕРАТОРЫ • ! && || • При задании operator&&

    теряется свойство «короткого замыкания».
  29. bool Function1() { return false; } bool Function2(); Function1() &&

    Function2(); ////////////////////////////////// MyBool Function3() { return MyBool::FALSE; } MyBool Function4(); bool operator&&(MyBool const &, MyBool const &); Function3() && Function4(); Function2() не будет вызвана, а Function4() – будет!
  30. ПРИСВОЕНИЕ- МОДИФИКАЦИЯ • += -= *= /= %= &= |=

    ^= <<= >>= • Метод класса! • operator+= не генерируется автоматически из operator= и operator+. • Нужно возвращать *this.
  31. ОПЕРАТОРЫ ИНКРЕМЕНТА И ДЕКРЕМЕНТА SomeValue& SomeValue::operator++() // префиксный { ++data;

    return *this; } SomeValue SomeValue::operator++(int unused) // постфиксный { SomeValue result = *this; ++data; return result; } SomeValue v; ++v; // префиксный ++ v++; // постфиксный ++
  32. // постфиксный SomeValue SomeValue::operator++(int unused) { SomeValue result = *this;

    ++(*this); // вызов SomeValue::operator++() return result; } Реализация постфиксного оператора 
 через префиксный
  33. • Должен быть членом класса. • Допустим один аргумент, зато

    любого типа. • Обычно переопределяется две версии: c const и без. ОПЕРАТОР ИНДЕКСА
  34. template <typename T> class StupidVector { // 100 элементов должно

    хватить всем! T array[100]; public: // ... T &operator[](size_t idx) { return array[idx]; } const T &operator[](size_t idx) const { return array[idx]; } };
  35. template <typename T> class Matrix; template <typename T> class MatrixColumn

    { public: MatrixColumn(Matrix *m, size_t r) : matrix(m), row(r) {} T &operator[](int col) { return matrix->element(row, col); } Matrix *matrix; size_t row; }; template <typename T> class Matrix { public: Matrix(int rows, int cols); // ... T &element(int row, int col); MatrixColumn<T> operator[](int row) { return MatrixColumn<T>(this, row); } }; Matrix<double> mat(3, 3); mat[2][2] = 10;
  36. ОПЕРАТОР ВЫЗОВА ФУНКЦИИ • operator() должен быть членом класса, других

    ограничений нет. • Активно используется в STL для создания функторов. • std::unary_function • std::binary_function • …
  37. template <class _Arg, class _Result> struct unary_function { typedef _Arg

    argument_type; typedef _Result result_type; }; template <class _Arg1, class _Arg2, class _Result> struct binary_function { typedef _Arg1 first_argument_type; typedef _Arg2 second_argument_type; typedef _Result result_type; }; template <class _Tp> struct plus : public binary_function<_Tp, _Tp, _Tp> { _Tp operator()(const _Tp& __x, const _Tp& __y) const { return __x + __y; } }; template <class _Tp> struct negate : public unary_function<_Tp, _Tp> { _Tp operator()(const _Tp& __x) const { return -__x; } };
  38. #include <iostream> #include <functional> #include <algorithm> using namespace std; int

    main () { int numbers[] {10, -20, -30, 40, -50}; int cx; cx = count_if(numbers, numbers+5, bind2nd(less<int>(), 0)); cout << "There are " << cx << " negative elements.\n"; return 0; }
  39. template <typename T> class undeletable_pointer { public: undeletable_pointer(T *ptr) :

    base(ptr) {} // ... private: void operator delete(void *); T *base; }; struct SomeObject { typedef undeletable_pointer<SomeObject> undeletable_ptr; undeletable_ptr operator&() { return this; } };
  40. class Err {}; class Giant {}; class Big { public:

    Big() { throw Err(); } }; class MyClass { Giant *giant; Big *big; public: MyClass(): giant(new Giant()), big(new Big()) {} ~MyClass() { delete giant; delete big; } }; int main() { try { MyClass myobject; } catch (Err) {} return 0; } К «умным» указателям
 (smart pointer) Решение: заменить указатели 
 объектами с объявленными 
 деструкторами
  41. template <typename T> class SmartPtr { T *ptr; public: SmartPtr(T

    *p) : ptr(p) {}; T& operator*() { return *ptr; } T* operator->() { return ptr; } ~SmartPtr() { delete ptr; } }; Первое приближение
  42. template <typename T> class SmartPtr { T *ptr; public: explicit

    SmartPtr(T *p = nullptr) : ptr(p) {} T& operator*() const { return *ptr; } T* operator->() const { return ptr; } SmartPtr(SmartPtr<T> &other) : ptr(other.release()) {} SmartPtr operator=(SmartPtr<T>& other) { if (this != &other) reset(other.release()); return *this; } ~SmartPtr() { delete ptr; } T *release() { T *oldPtr = ptr; ptr = nullptr; return oldPtr; } void reset(T *newPtr) { if (ptr != newPtr) { delete ptr; ptr = newPtr; } } };
  43. ПРЕОБРАЗОВАНИЕ ТИПА class Y { // ... }; ostream &operator<<(ostream

    &os, const Y &y); class X { // ... operator bool() const; operator Y() const; }; X x; if (x) { // ... } Y y(x); cout << x;
  44. class complex { double re, im; public: complex(double r =

    0, double i = 0) : re(r), im(i) {} // ... }; bool operator==(complex, complex); void f(complex x, complex y) { x==y; // operator==(x,y) x==3; // operator==(x,complex(3)) 3==y; // operator==(complex(3),y) } Полезное неявное преобразование
  45. Неполезное неявное преобразование class Date { int d, m, y;

    public: Date(int dd = today.d, int mm = today.m, int yy = today.y); // ... }; void my_fct(Date d); void f() { Date d {15}; // 15-е число этого месяца // ... my_fct(15); // ОЙ d = 15; // хм... // ... }
  46. • Если у класса объявлен конструктор с одним аргументом, компилятор

    может делать «преобразование типа» путем создания временного объекта. • Ключевое слово explicit запрещает компилятору преобразовывать типы.
  47. void f(const Y &); class Y { Y(const X &);

    }; X x; f(x); ///////////////// class Array { public: Array(int size); }; Array a('?'); void f(const Y &); class Y { explicit Y(const X &); }; X x; f(x); // ОШИБКА ///////////////// class Array { public: explicit Array(int size); }; Array a('?'); // ОШИБКА
  48. struct A { // неявное преобразование в int operator int()

    const { return 100; } // явное преобразование в std::string explicit operator std::string() const { return "explicit"; } }; int main() { A a; int i = a; // OK - неявное преобразование std::string s = a; // Ошибка. Нужно явное преобразование std::string t = static_cast<std::string>(a); // OK } explicit преобразование типов
  49. NEW И DELETE // Глобальные функции void* operator new(size_t); void*

    operator new[](size_t); void operator delete(void *, size_t); void operator delete[](void *, size_t);
  50. // "Placement" new class Arena { public: Arena(int x) {};

    ~Arena() {}; // ... }; const int n = 100; Arena* placementMem = static_cast<Arena*>(operator new[] (n* sizeof(Arena))); for(int i = 0; i < n; ++i){ new (placementMem + i) Arena(rand()); } for(int i = 0; i < n; ++i){ placementMem[i].~A(); } operator delete[] (placementMem);
  51. class Employee { public: // ... void *operator new(size_t); void

    operator delete(void∗, size_t); void *operator new[](size_t); void operator delete[](void∗, size_t); }; class Manager : public Employee { /* ... */ }; Employee *p = new Manager; // ... delete p; // нужен виртуальный деструктор! // sizeof(Manager) будет передан вторым аргументом // в operator delete. Обычные new и delete для конкретного класса
  52. class X { public: // ... void operator=(const X&) =

    delete; void operator&() = delete; void operator,(const X&) = delete; // ... }; void f(X a, X b) { a = b; // ОЙ &a; // ОЙ-ОЙ a,b; // ОЙ-ОЙ-ОЙ } Удаление дефолтных версий операторов
  53. ОПЕРАТОРЫ И ПРОСТРАНСТВА ИМЁН Для бинарного оператора x@y, где x

    имеет тип X, а y – тип Y: • Если X – класс, поискать operator@ как член X или его базового класса. • Поискать operator@ в контексте выражения x@y. • Если X объявлен в пространстве имён N, поискать operator@ в этом пространстве имён. • Если Y объявлен в пространстве имён M, поискать operator@ в этом пространстве имён.
  54. ОПЕРАТОРЫ, КОТОРЫЕ НЕЛЬЗЯ ПЕРЕГРУЗИТЬ • ? : (тернарный) • .

    (доступ к члену класса) • .* (доступ к члену класса по указателю) • :: (namespace) • sizeof • alignof • typeid