Форум программистов, компьютерный форум CyberForum.ru

C++

Войти
Регистрация
Восстановить пароль
 
Рейтинг: Рейтинг темы: голосов - 13, средняя оценка - 4.77
Kastaneda
Форумчанин
Эксперт С++
4468 / 2830 / 224
Регистрация: 12.12.2009
Сообщений: 7,199
Записей в блоге: 1
Завершенные тесты: 1
#1

С++ идиомы - C++

31.07.2016, 19:20. Просмотров 3085. Ответов 7
Метки нет (Все метки)

Перевод статей 1 и 2. Будет постепенно обновляться. Желающие внести вклад могут писать в ЛС.

 Комментарий модератора 
Тема открыта, просьба добавлять только посты с переводом, обсуждение здесь


Переведенные идиомы:
self-assignment in an assignment operator
Scope Guard
Shrink-to-fit
Checked delete
Pointer To Implementation
Получение адреса(ака взятие адреса aka Address-of)
nullptr
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
31.07.2016, 19:20     С++ идиомы
Посмотрите здесь:

С++ идиомы - обсуждение C++
Английские идиомы: как правильно перевести in its own right?

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
HelicopterK52
650 / 193 / 28
Регистрация: 27.07.2016
Сообщений: 475
Завершенные тесты: 1
01.08.2016, 11:33     С++ идиомы #2
Scope Guard

Задачи:
Гарантировать освобождение ресурсов при возникновении исключения, но не освобождать ресурсы при нормальном завершении.
Предоставить базовую гарантию безопасности исключений.

Мотивация:
Идиома RAII позволяет захватывать ресурсы в конструкторе и освобождать их в деструкторе, при достижении конца области видимости или из-за исключения. В RAII ресурс освобождает всегда. Это может быть не очень гибким решением. Может потребоваться освободить ресурсы в случае возникновения исключения и не освобождать при нормальном завершении.

Решение и пример кода:
Типичная реализация идиомы RAII с проверкой необходимости освобождения ресурса.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ScopeGuard
{
public:
  ScopeGuard () 
   : engaged_ (true) 
  { /* Захват ресурса */ }
  
  ~ScopeGuard ()  
  { 
    if (engaged_) 
     { /* Освобождение ресурса */} 
  }
  void release () 
  { 
     engaged_ = false; 
     /* Ресурс не будет освобожден в деструкторе */ 
  }
private:
  bool engaged_;
};
void some_init_function ()
{
  ScopeGuard guard;
  // В случае возникновения исключения ресурс будет освободжен
  // прим. переводчика: в случае каких-то других ошибок мы можем просто выйти из функции, ресурс также будет освобожден
  guard.release (); // При нормальном завершении ресурс освобождать не нужно
}
Kastaneda
Форумчанин
Эксперт С++
4468 / 2830 / 224
Регистрация: 12.12.2009
Сообщений: 7,199
Записей в блоге: 1
Завершенные тесты: 1
01.08.2016, 11:54  [ТС]     С++ идиомы #3
self-assignment in an assignment operator (самоприсваивание в операторе присваивания)

T::operator= который обрабатывает случай, где левый и правый операнды являются одним и тем же объектом.
C++
1
2
3
4
5
6
7
8
9
T& operator= (const T& that)
{
    if (this == &that)
        return *this;
 
    // handle assignment here
 
    return *this;
}
Замечания:

Понимайте разницу между идентичностью (левый и правый операнды являются одним объектом) и одинаковостью (левый и правый операнды имеют одинаковые значения).T::operator= должен защищать себя в случае индетичности объектов, код присваивания может быть удобным и безопастным предполагая, что работает с разными объектами.

Существуют и другие техники, которые в некоторых случаях могут быть более удобными, но они не применимы во всех ситуациях. Например если все члены класса T (скажем mem1, mem2, ..., memN) предоставляют функцию swap(), то можно использовать следующий код
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
T& operator= (T that)
{
    // that создан конструктором копий
 
    mem1.swap (that.mem1);
    mem2.swap (that.mem2);
 
    ...
 
    memN.swap (that.memN);
 
    // теперь то, что изначально было  this->mem1, this->mem2, etc. разрушается
    // при разрушении объекта that 
    // новые данные лежат в *this 
 
    return *this;
}
HelicopterK52
650 / 193 / 28
Регистрация: 27.07.2016
Сообщений: 475
Завершенные тесты: 1
01.08.2016, 20:51     С++ идиомы #4
Shrink-to-fit (уменьшить до размеров)

Задачи:
Уменьшить ёмкость (capacity) контейнера, до размера, достаточного для содержания элементов.

Также известен как
"Swap-To-Fit", введенный Скотом Майерсом в книге "Effective STL" ("Эффективное использование STL").

Мотивация:
Контейнеры в стандартной библиотеке часто выделяют памяти под большее число элементов, чем находится в контейнере.
Это позволяет оптимизировать расширение контейнера за счет более редких выделений памяти.
Но, когда контейнер уменьшается (или когда запас израсходаван не полностью, прим. переводчика), память так и остается занятой, хотя, фактически, она не используется. Это не нужный перерасход памяти. Данная (shrink-to-fit) идиома была разработана чтобы уменьшить расход до минимального требуемого контейнеру количества памяти, тем самым экономя ресурсы.

Решение и пример кода:
Данная идиома очень проста, как показано ниже.
C++
1
2
3
std::vector<int> v;
//v будет обменен с временной копией, которая оптимально использует запас памяти
std::vector<int>(v).swap(v);
Первая половина этой инструкции - std::vector<int>(v), создает временный вектор целых чисел, гарантируя, что выделенной памяти достаточно для хранения всех элементов вектора v, который передан в параметре. Также эти элементы копируются во временный вектор.
Вторая половина инструкции - .swap(v) обменивает элементы вектора v и временного вектора, используя не выбрасывающую исключений функцию-член swap, что является очень эффективным средством, которое сводится к обмену внутренних указателей между векторами или чуть более того.
После этого временный вектор удаляется, очищая память и удаляя элементы, которые первоначально находились в векторе v.
Вектор v же имеет ровно столько памяти, сколько нужно для хранения элементов.

ISO/IEC 14882:1998 не гарантирует такое поведение для конструктора копирования. Как гарантировать такое поведение?

Более надежным решением (в частности std::string и std::vector могут быть реализованы с использованием подсчета ссылок и тогда конструктор копирования может "скопировать" всю избыточную память) будет использование конструктора диапазонов, вместо конструктора копирования:
C++
1
2
3
std::vector<int> v;
//v будет обменен с временной копией, которая оптимально использует запас памяти
std::vector<int>(v.begin(), v.end()).swap(v);
Решение в C++11:
В C++11 некоторые контейнеры предоставляют функцию-член shrink_to_fit, например vector, deque, basic_string. shrink_to_fit запрашивает уменьшение capacity до size, Но данный запрос не является обязательным к исполнению.
HelicopterK52
650 / 193 / 28
Регистрация: 27.07.2016
Сообщений: 475
Завершенные тесты: 1
01.08.2016, 20:51     С++ идиомы #5
Checked delete

Цель:
Повышение безопасности при использовании delete expression.

Мотивация и пример проблемного кода:
Стандарт C++ (пункт 5.3.5/5) позволяет использовать в delete-expression указатель на не полный тип, но при этом, если деструктор или функция освобождения памяти объекта не являются тривиальными, то это приведет к неопределенному поведению.
Цитата Сообщение от 5.3.5/5
If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
Некоторые компиляторы в таком случае выдают предупреждение, но могут этого и не сделать, либо программист может игнорировать или отключить предупреждения.

В следующем примере в main.cpp определяется объект типа Object. В функции main() вызывается функция delete_object(), определенная в deleter.cpp, где нет определения класса Object, а есть лишь его объявление.
Вызов delete в данной функции приводит к неопределенному поведению.
C++
1
2
3
4
5
6
////////////////////
// File: deleter.hpp
////////////////////
// Объявляем Object, но не определяем его
struct Object;
void delete_object(Object* p);
C++
1
2
3
4
5
6
7
////////////////////
// File: deleter.cpp
////////////////////
#include "deleter.hpp"
 
// Удаляем объект типа Object, не имея его определения
void delete_object(Object* p) { delete p; }
C++
1
2
3
4
5
6
7
8
9
10
11
////////////////////
// File: object.hpp
////////////////////
struct Object
{
  //Этот деструктор не будет вызван при удалении, 
  //если не будет определения типа при вызове delete
  ~Object() {
     // ...
  }
};
C++
1
2
3
4
5
6
7
8
9
10
////////////////////
// File: main.cpp
////////////////////
#include "deleter.hpp"
#include "object.hpp"
 
int main() {
  Object* p = new Object;
  delete_object(p);
}
Решение и пример кода:
Идиома checked delete полагается на вызов шаблонной функции для удаления объекта, а не на прямой вызов delete, который может привести к неопределенному поведению для объявленных, но неопределенных типов.
Ниже приводится реализация шаблонна функции boost::checked_delete из Boost Utility library.
Её использование вызывает ошибку компиляции при использовании sizeof для параметра шаблона T, если T - не полный тип.
Если T объявлен, но не определен, то sizeof(T) будет генерировать ошибку компиляции или возвращать нулевое значение, в зависимости от компилятора.
Если sizeof(T) вернет ноль, то произойдет ошибка компиляции, т.к. объявляется массив с отрицательным количеством элементов (-1). Имя type_must_be_complete в данном случае появляется в сообщении об ошибке и позволяет понять что произошло.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<class T> 
inline void checked_delete(T * x)
{
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete x;
}
template<class T> 
struct checked_deleter : std::unary_function <T *, void>
{
    void operator()(T * x) const
    {
        boost::checked_delete(x);
    }
};
Примечание: этот метод также применим к delete[].

Предупреждение: std::auto_ptr не использует никакого эквивалента checked delete. Поэтому инстанцирование std::auto_ptr с неполным типом может привести к неопределенному поведению в деструкторе, если в момент объявления std::auto_ptr тип параметра шаблона определен не полностью.
Kastaneda
Форумчанин
Эксперт С++
4468 / 2830 / 224
Регистрация: 12.12.2009
Сообщений: 7,199
Записей в блоге: 1
Завершенные тесты: 1
02.08.2016, 08:12  [ТС]     С++ идиомы #6
Pointer To Implementation (pImpl)

Идиома "pointer to implementation" (pImpl) также называется "opaque pointer" (дословный перевод "непрозрачный указатель"), это способ предоставления данных и в еще один уровень абстракции в реализации классов.

В С++ вы должны написать декларацию переменных-членов класса внутри определения класса, эти члены должны быть публичны (прим. переводчика - думаю речь идет об интерфейсе, иначе зачем членам быть публичными) и поскольку для членов выделяется память абстракция реализации не возможна для "всех" классов.
Тем не менее, за счет дополнительного указателя и вызова функции, вы можете иметь такой уровень абстракции через указатель на реализацию.

Допустим вы написали такой класс:
C++
1
2
3
4
5
6
7
class Book
{
public:
  void print();
private:
  std::string  m_Contents;
}
Кто-то, кто работает с классом Book должен знать лишь о методе print(), но что произойдет если вы хотите добавить больше деталей в ваш класс:
C++
1
2
3
4
5
6
7
8
class Book
{
public:
  void print();
private:
  std::string  m_Contents;
  std::string  m_Title;
}
В этом случае все, кто используют ваш класс, должны перекомпилировать свой код, несмотря на то, что они по прежнему используют только вызов метода print().

pImpl может реализовывать следующий паттерн таким образом, что описанная выше ситуация не является проблемой.
C++
1
2
3
4
5
6
7
8
9
10
11
/* public.h */
class Book
{
public:
  Book();
  ~Book();
  void print();
private:
  class BookImpl;
  BookImpl* m_p;
}
а в отдельном "внутреннем" хедере
C++
1
2
3
4
5
6
7
8
9
10
11
/* private.h */
#include "public.h"
#include <iostream>
class Book::BookImpl
{
public:
  void print();
private:
  std::string  m_Contents;
  std::string  m_Title;
}
Реализация методов класса Book может выглядеть так

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Book::Book()
{
  m_p = new BookImpl();
}
 
Book::~Book()
{
  delete m_p;
}
 
void Book::print()
{
  m_p->print();
}
 
/* функции BookImpl */
 
void Book::BookImpl::print()
{
  std::cout << "print from BookImpl" << std::endl;
}
Используем Book в функции main:
C++
1
2
3
4
5
6
int main()
{
  Book *b = new Book();
  b->print();
  delete b;
}
Вы также можете использовать std::unique_ptr<BookImpl> или эквивалент для управления внутренним указателем.
2ima
☆ Форумчанин(FSC)☆
910 / 289 / 9
Регистрация: 28.04.2013
Сообщений: 2,375
Записей в блоге: 10
Завершенные тесты: 1
09.08.2016, 19:40     С++ идиомы #7
Получение адреса(ака взятие адреса aka Address-of)

Назначение:
Поиск адреса обьекта класса который имеет перегруженный унарный оператор "амперсанд"(&).

Интерес:
Язык С++ разрешает перегрузку унарного амперсанда (&) для классовых типов. Тип возвращаемого значения не обязательно должен быть реальным адресом обьекта . Предназначение такого класса довольно спорно, но все же язык позволяет это.Идиома взятия адреса это способ получения реального адреса обьекта, независимого от перегруженного унарного амперсанда и его инкапсулированости.

В примере ниже не удается скомпилировать функцию main потому что оператор & класса nonaddressable является закрытым членом этого класса.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
class nonaddressable 
{
public:
    typedef double useless_type;
private:
    useless_type operator&() const;
};
 
int main()
{
  nonaddressable na;
  nonaddressable * naptr = &na; // Здесь ошибка компиляции
}
Решение и пример использования
Идиома получения адреса запрашивает адрес обьекта используя серию преобразований

C++
1
2
3
4
5
6
7
8
9
10
template <class T>
T * addressof(T & v)
{
  return reinterpret_cast<T *>(& const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
}
int main()
{
  nonaddressable na;
  nonaddressable * naptr = addressof(na); // ошибки больше нет
}
Где используется?
Используется в библиотеке boost для получения адреса
Эта функция уже пристутствует в заголовке <memory> нового стандарта C++(C++ 11)
meJevin
154 / 146 / 57
Регистрация: 18.11.2015
Сообщений: 629
Завершенные тесты: 1
16.08.2016, 15:46     С++ идиомы #8
Nullptr

Задачи:
Научиться отличать число 0 от нулевого указателя


Мотивация:

На протяжении многих лет С++ имело позорный недостаток, у С++ не было ключевого слова, которое бы обозначало нулевой указатель. С++ 11 избавился от этого недостатка. Строгая типизация С++ делает определение глобальной константы NULL в стиле языка C почти что бесполезной в выражениях, например:
C++
1
2
3
4
5
6
7
8
9
10
// Если бы мы попробовали определить NULL в C++, вот так
#define NULL ((void *)0)
 
// То тогда...
 
char * str = NULL; // Ошибка: Нельзя автоматически привести тип void * к char *
 
void (C::*pmf) () = &C::func;
 
if (pmf == NULL) {} // Ошибка: Нельзя автоматически привести тип void * к типу указателя на функцию-член
В С++
C++
1
#define NULL 0
и
C++
1
#define NULL 0L
являются правильными (но не совсем) определениями константы нулевого указателя.

Проблема самого первого примера в том, что С++ запрещает приведение из типа void *, даже когда его значение является константным нулем. Но, для простого константного нуля С++ имеет преобразование int в указатель (еще short в указатель, long в указатель и т.п.).Это имеет свои минусы. Приведем пример на работе с перегруженными функциями:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
 
#define NULL 0
 
void func(int var) { std::cout << "Int" << std::endl; };
void func(double * ptr) { std::cout << "Double *" << std::endl; };
 
int main()
{
    func(static_cast <double *>(NULL)); // вызывается func(double *), как мы и хотели
 
    func(NULL); // запись интерпретируется, как func(0) - вызывается func(int).
                // но программист, скорее всего, хотел вызвать func(double *),
                // потому что NULL является константой нулевого УКАЗАТЕЛЯ,
                // которую мы определили ранее.
 
    return 0;
}
После обработки кода препроцессором, код в функции main превратиться в:
C++
1
2
3
4
5
func(static_cast <double *>(0));
 
func(0);
 
return 0;
После запуска этого кода, на экране будет:
Код
Double *
Int
Таким образом, чтобы использовать #define NULL 0 так, как задумывалось (с указателями), нужно всегда писать что типа static_cast<double *>(NULL)


Использование #define NULL 0 имеет свою кучку проблем. Помимо проблемы с перегруженными функциями, C++ требует того, чтобы NULL было определено целочисленным константным выражением со значением 0. Поэтому, в отличии от С, нулевой указатель не может быть определен как ((void *)0) в стандартной библиотеке C++. Более того, конкретная форма определения оставлена для той или иной реализации, что значит, что 0 и 0L - подходящие определения, наряду с некоторыми другими.



Решение и пример использования
Идиома нулевого указателя решает некоторые вышеперечисленные проблемы и может быть использована снова и снова. Будущий пример является очень близким по функционалу решением, относительно ключевого слова nullptr, добавленного в С++ 11, и использует только стандартные приемы, которые были доступны еще до С++ 11.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const // Объект константный...
class nullptr_t 
{
  public:
    template<class T>
    inline operator T*() const // Может быть приведен к любому типу нулевого указателя (не на член класса)
    { return 0; }
 
    template<class C, class T>
    inline operator T C::*() const   // или любому типу нулевого указателя на член
    { return 0; }
 
  private:
    void operator&() const;  // мы не можем взять адрес nullptr
 
} nullptr = {};
Код ниже показывает, как можно использовать класс, определенный выше (предполагается, что пользователь уже сделал #include класса выше)

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <typeinfo>
//#include "nullptr_t"
struct C
{
    void func();
};
 
template<typename T>
void g(T* t) {}
 
template<typename T>
void h(T t) {}
 
void func(double *) {}
void func(int) {}
 
int main(void)
{
    char * ch = nullptr;        // Ок
    func(nullptr);             // Вызывает func(double *)
    func(0);                   // Вызывает func(int)
    void (C::*pmf2)() = 0;      // Ок
    void (C::*pmf)() = nullptr; // Ок
    nullptr_t n1, n2;
    n1 = n2;
    nullptr_t *null = &n1;    // Адрес мы взять не можем
    if (nullptr == ch) {}       // Ок
    if (nullptr == pmf) {}      // Это тоже работает, но не на g++ 4.1.1-4.5 из-за бага #33990
                                // для GCC 4: if ((typeof(pmf))nullptr == pmf) {}
    const int n = 0;
    if (nullptr == n) {}        // Не должно компилироваться, но только Comeau показывает ошибку
 
    int p = 0;
    if (nullptr == p) {}      // Не работает
    g (nullptr);              // Не возможно вывести тип T
 
    int expr = 0;
    char* ch3 = expr ? nullptr : nullptr; // ch3 - нулевой указатель
 
    char* ch4 = expr ? 0 : nullptr;     // ошибка, типы не совместимы
    int n3 = expr ? nullptr : nullptr;  // ошибка, нельзя преобразовать nullptr в int
    int n4 = expr ? 0 : nullptr;        // ошибка, типы не совместимы
 
    h(0);                // Выводит T = int
    h(nullptr);          // Выводит T = nullptr_t
    h((float*) nullptr); // Выводит T = float*
 
    sizeof(nullptr);     // Ок
    typeid(nullptr);     // Ок
    throw nullptr;       // Ок
}
К сожалению, похоже, есть баг в компиляторе gcc 4.1.1, который не распознает сравнение nullptr с указателем на функцию-член (pmf). Код выше успешно компилируется, если убрать строки с ошибками, которые мы допустили в демонстрационных целях (26, 31, 34, 35, 40, 41, 42, 50)

Заметьте, что идиома нулевого указателя использует идиому Return Type Resolver, чтобы автоматически вывести нулевой указатель правильного типа, в зависимости от типа объекта, к которому мы его присваиваем. Например, если nullptr присваивают к char *, создается функция преобразования с char параметром шаблона.


Последствия
Есть некоторые недостатки этого приема, в список этих недостатков входят следующие пункты:
  • Чтобы использовать nullptr, надо постоянно писать #include "nullptr_t". В С++ 11 и выше nullptr является ключевым словом и не требует включения заголовочного файла (но для использования std::nullptr_t заголовочный файл включать все равно надо (<cstddef>))
  • Компиляторы, по историческим причинам, произвели неудовлетворительные диагностики, когда был использован вышепоказанный код
Yandex
Объявления
16.08.2016, 15:46     С++ идиомы
Ответ Создать тему
Опции темы

Текущее время: 06:32. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru