C++11 in action

By Mikhail Matrosov

Содержание

Небольшие полезности

nullptr

Ключевое слово, явно обозначающее нулевой указатель

  • Имеет тип nullptr_t, а 0 и NULL имеют тип int
  • NULL понятнее, чем 0, а nullptr безопаснее, чем NULL

void foo(int) { cout << "int" << endl; }
void foo(void*) { cout << "void*" << endl; }

int main() {
  foo(0);        // int
  foo(nullptr);  // void*
}
                    

static_assert

Проверка условия на этапе компиляции


// Compilation error on x64 configuration
static_assert(sizeof(void *) == 4, 
  "64-bit code generation is not supported.");

template<class T, int C> class Stack {
  T* m_pBuffer[C];
  static_assert(C > 0, "Stack capacity must be positive.");
public:
  // Member function definition
};

const int g_Count = 150;

int main() {
  static_assert(g_Count < 100, 
    "Count must not exceed 100.");  // Compilation error
  Stack<int, 10> s1; // OK
  Stack<int, 0> s2;  // Compilation error
}
                    

Малоиспользуемые контейнеры

std::tuple

Кортеж из нескольких элементов

  • Обобщение std::pair
  • Произвольное количество элементов
  • Произвольные типы элементов
  • Определено отношение порядка

Работа с std::tuple


// Explicit types and initialization
std::tuple<int, double, std::string> t1(42, 3.14, "foo");
  
// Deduced types, same as above
auto t2 = std::make_tuple(42, 3.14, std::string("foo"));

// Explicit retrieval of elements
int i = std::get<0>(t2);
double d = std::get<1>(t2);
std::string s = std::get<2>(t2);

// Batch retrieval of all elements, same as above
std::tie(i, d, s) = t2;

// Batch retrieval of several elements
std::tie(i, std::ignore, s) = t2;
                    

Функциональный вид


void FindFurthestVertices(const Graph& graph, int& v1, int& v2, double& d);

void foo(const Graph& graph)
{
  int v1, v2;
  double d;

  FindFurthestVertices(graph, v1, v2, d);
}
                    

Функциональный вид


std::tuple<int, int, double> FindFurthestVertices(const Graph& graph);

void foo(const Graph& graph)
{
  int v1, v2;
  double d;

  std::tie(v1, v2, d) = FindFurthestVertices(graph);
}
                    

Какая альтернатива в C++98?

std::tuple vs. struct

  • Pros:
    • Не создаётся дополнительной сущности
    • Автоматически определяется конструктор
    • Автоматически определяется отношение порядка
  • Cons:
    • Страшный std::get<>
    • Нельзя именовать поля

std::array

Обёртка для встроенных массивов в стиле C++

  • Размер известен на этапе компиляции
  • Целиком располагается в стеке
  • Стандартный контейнер STL
  • Определены операции копирования и присваивания
  • Определено отношение порядка

Работа с std::array


const int count = 5;
// Supports initialization lists
std::array<int, count> a = { 2, 3, 5, 7, 11 };
// Guaranteed to be stored just like native array
static_assert(sizeof(a) == count * sizeof(int), "Non-standard");
// Supports copying and assignment
std::array<int, 5> b = a;
// Some useful built-in methods
a.fill(0);
// Supports comparison
if (a < b) std::cout << "Works!" << std::endl;  // Really works
// Provide direct access to data
int* pB = b.data();
// Supports common STL-container interface
std::cout << a.size() << std::endl;  // 5
for (auto i = b.cbegin(); i != b.cend(); ++i) std::cout << *i << " ";
                    

built-in arrays и функции


typedef double Color[3];

Color CropColor(Color color)  // Shallow copy of built-in array
{
  for (int i = 0; i < 3; ++i)
  {
    // Crop into [0; 1] range
    color[i] = std::max(0.0, std::min(color[i], 1.0));
  }

  return color;  // Cannot return built-in array
}					
                    

std::array и функции


typedef std::array<double, 3> Color;

Color CropColor(Color color)  // Deep copy of std::array
{
  for (int i = 0; i < 3; ++i)
  {
    // Crop into [0; 1] range
    color[i] = std::max(0.0, std::min(color[i], 1.0));
  }

  return color;  // Return is ok
}					
                    

Что использовать?

Практически всегда следует пользоваться std::array. Возможные исключения:

  • Для константных списков, размер которых может меняться
  • Для поддержания единого стиля

enum Status;

const char* GetErrorMessage(Status status)
{
  static const char* messages[] = {
    "OK",                            // STATUS_OK
    "Unknown error",                 // STATUS_ERROR
    "Not enough memory",             // STATUS_MEMORY
    "Filesystem error"               // STATUS_IO
  };
  return messages[status];
}
                    

std::array vs. built-in array

  • Pros:
    • STL-совместимый интерфейс
    • Можно копировать и присваивать
    • Можно передавать и возвращать по значению
    • Можно сравнивать
    • Многомерные варианты не взрывают мозг
  • Cons:
    • Нет автоматического определения размера из списка инициализации

std::array, std::tuple, std::pair

  • Размер известен на этапе компиляции
  • Не выделяют динамической памяти
  • Определено отношение порядка
  • Поддерживают std::get<>
  • Обычно легковесные объекты

RAII & ScopeGuard

Лирическое отступление

Из докалада Скотта Мейерса "Why C++ Sails When the Vasa Sank", почему С++ так популярен, несмотря на сложность

  • Совместимость с С
    • 42% всего (открытого) кода в мире написаны на С
  • Очень гибкие возможности
    • Деструкторы
    • Шаблоны
    • Перегрузка
  • Мультипарадигменность
    • Процедурное программирование (STL)
    • Объектно-ориентированное (классы)
    • Обобщённое программирование (шаблоны)
    • Функциональное программирование (лямбды)

RAII

Resource Acquisition Is Initialization

  • Владение ресурсом ассоциируется с временем жизни объекта
    • Конструктор (initialization) захватывает ресурс (acquisition)
    • Деструктор освобождает ресурс
  • Момент вызова деструктора чётко определён, следовательно знаем точный момент освобождения ресурса
  • Альтернативные названия:
    • CADRe (Constructor Acquires Destructor Releases)
    • SBRM (Scope Bounded Resource Management)

RAII


void WriteToFile(const std::string& message) 
{
  // mutex to protect file access
  static std::mutex mutex;

  // lock mutex before accessing file
  std::lock_guard<std::mutex> lock(mutex);

  // try to open file
  std::ofstream file("example.txt");
  if (!file.is_open())
    throw std::runtime_error("unable to open file");

  // write message to file
  file << message << std::endl;

  // file will be closed 1st when leaving scope (regardless of exception)
  // mutex will be unlocked 2nd (from lock destructor) when leaving
  // scope (regardless of exception)
}
                    

RAII

Владение ресурсом ассоциируется с временем жизни объекта

  • Стратегия управления любыми ресурсами
    • GC управляет только памятью
  • Безопасность при исключениях
  • Безопасность при нескольких точках возврата

Примеры:

  • std::fstream - управление файлом
  • std::vector, std::shared_ptr - управление памятью
  • std::lock_guard - управление разделяемым ресурсом
  • new в конструкторе, delete в деструкторе

ScopeGuard

Класс, который выполняет заданную функцию в деструкторе. Позволяет использовать идиому RAII там, где её не хватает.

  • Управление любым собственным ресурсом
  • Локализация кода инициализации/деинициализации
  • Другие неожиданные применения

Код ScopeGuard.h


class ScopeGuard
{
public:
  ScopeGuard(std::function<void()> f) : m_f(f) {}

  ~ScopeGuard() 
  { 
    if (m_f)
    {
      try 
      {
        m_f(); 
      }
      catch (std::exception&)
      {
        // Some assertions or logging here
      }
    }
  }

  // Disable copy operations, enable move operations

private:
  std::function<void()> m_f;
};
                    

Восстановление состояния


class IHardware
{
public:
  virtual double GetPosition() const = 0;
  virtual void SetPosition(double position) = 0;
  virtual bool TakeSnapshot() = 0;  // Might fail
};

bool DoScan(double step, int count, IHardware* pHardware)
{
  double startPos = pHardware->GetPosition();
  double pos = startPos;

  for (int i = 0; i < count; i++)
  {
    pos += step;
    pHardware->SetPosition(pos);
    pHardware->TakeSnapshot();
  }

  pHardware->SetPosition(startPos);

  return true;
}
                    

Восстановление состояния


class IHardware
{
public:
  virtual double GetPosition() const = 0;
  virtual void SetPosition(double position) = 0;
  virtual bool TakeSnapshot() = 0;  // Might fail
};

bool DoScan(double step, int count, IHardware* pHardware)
{
  double startPos = pHardware->GetPosition();
  double pos = startPos;

  for (int i = 0; i < count; i++)
  {
    pos += step;
    pHardware->SetPosition(pos);
    if (!pHardware->TakeSnapshot()) return false;
  }

  pHardware->SetPosition(startPos);

  return true;
}
                    

Восстановление состояния


class IHardware
{
public:
  virtual double GetPosition() const = 0;
  virtual void SetPosition(double position) = 0;
  virtual bool TakeSnapshot() = 0;  // Might fail
};

bool DoScan(double step, int count, IHardware* pHardware)
{
  double startPos = pHardware->GetPosition();
  double pos = startPos;

  for (int i = 0; i < count; i++)
  {
    pos += step;
    pHardware->SetPosition(pos);
    if (!pHardware->TakeSnapshot()) 
    {
      pHardware->SetPosition(startPos);  // DRY!
      return false;
    }
  }

  pHardware->SetPosition(startPos);

  return true;
}
                    

Восстановление состояния


class IHardware
{
public:
  virtual double GetPosition() const = 0;
  virtual void SetPosition(double position) = 0;
  virtual bool TakeSnapshot() = 0;  // Might fail
};

bool DoScan(double step, int count, IHardware* pHardware)
{
  double startPos = pHardware->GetPosition();
  double pos = startPos;

  ScopeGuard posGuard([&] () { pHardware->SetPosition(startPos); });

  for (int i = 0; i < count; i++)
  {
    pos += step;
    pHardware->SetPosition(pos);
    if (!pHardware->TakeSnapshot()) return false;
  }

  return true;
}
                    

Безопасный С-код


cmsHPROFILE cmsCreateProfilePlaceholder(cmsContext ContextID);
cmsBool cmsCloseProfile(cmsHPROFILE hProfile);

void CreateAndSaveColorProfile(const std::string& path)
{
  cmsHPROFILE hProfile = cmsCreateProfilePlaceholder(0);

  ScopeGuard profileGuard([&] () { cmsCloseProfile(hProfile); });

  // Create and save profile, both operations might throw
}
                    

Дополнительные функции


class ScopeGuard
{
  ...

  void Discard()
  {
    m_f = nullptr;
  }

  void Apply()
  {
    m_f();
    m_f = nullptr;
  }

  ...
};
                    

Досрочное освобождение ресурса


bool DoScan(double step, int count, IHardware* pHardware)
{
  double startPos = pHardware->GetPosition();
  double pos = startPos;

  ScopeGuard posGuard([&] () { pHardware->SetPosition(startPos); });

  for (int i = 0; i < count; i++)
  {
    pos += step;
    pHardware->SetPosition(pos);
    if (!pHardware->TakeSnapshot()) return false;
  }
  
  posGuard.Apply();

  // Time consuming operation, like saving snapshots

  return true;
}
                    

Обработка ошибок


class IImageStitcher
{
public:
  virtual msa::Status Initialize() = 0;
  virtual msa::Status StitchImages(const msa::ICollection* pImages) = 0;
  virtual const char* GetStatusDescr() const = 0;
};

bool Stitch(IImageStitcher* pStitcher)
{
  msa::ICollection* pImages;

  // Populate collection with images

  pStitcher->Initialize();
  pStitcher->StitchImages(pImages);

  return true;
}
                    

Обработка ошибок


bool Stitch(IImageStitcher* pStitcher)
{
  msa::ICollection* pImages;

  // Populate collection with images

  if (pStitcher->Initialize() != msa::STATUS_OK)
  {
    std::cout << pStitcher->GetStatusDescr() << std::endl;
    return false;
  }
  if (pStitcher->StitchImages(pImages) != msa::STATUS_OK)
  {
    std::cout << pStitcher->GetStatusDescr() << std::endl;
    return false;
  }

  return true;
}
                    

Обработка ошибок


bool Stitch(IImageStitcher* pStitcher)
{
  msa::ICollection* pImages;

  // Populate collection with images

  ScopeGuard errorGuard([&] () { 
    std::cout << pStitcher->GetStatusDescr() << std::endl; 
  });

  if (pStitcher->Initialize() != msa::STATUS_OK) return false;
  if (pStitcher->StitchImages(pImages) != msa::STATUS_OK) return false;

  errorGuard.Discard();

  return true;
}
                    

Значения по умолчанию


bool InitializeData(Data& data)
{
  ScopeGuard defaultGuard([&] () { 
    data = Data::GetDefaultValue(); 
  });

  if (!DoFirstInitializationStep(data)) return false;
  if (!DoSecondInitializationStep(data)) return false;
  if (!DoThirdInitializationStep(data)) return false;

  defaultGuard.Discard();

  return true;
}
                    

Транзакции


void SaveData(const Data& data)
{
  SaveDataToDatabase(data);  // Might throw
  ScopeGuard databaseGuard([&] () { RemoveDataFromDatabase(data); });

  SaveDataToDisk(data);  // Might throw
  ScopeGuard diskGuard([&] () { RemoveDataFromDisk(data); });
                          
  SaveDataToCloud(data);  // Might throw
  
  diskGuard.Discard();
  databaseGuard.Discard();
}
                    

RAII: заключение

  • Одна из ключевых парадигм в С++
  • Заиграла новыми красками с приходом С++11
  • ScopeGuard заботится об освобождении ресурса сразу после его получения
  • ScopeGuard позволяет мыслить наперёд в различных ситуациях

boost & NuGet

При чём тут NuGet?

Многопоточность в C++11

  • Пока сыровата
  • Пришла из boost
  • boost очень тяжёлый

Сборки в С++

Огромное количество вариантов сборки

  • Configuration: Debug/Release
  • CRT: Static/Dynamic
  • Library: Static/Dynamic
  • Platform: x86/x64
  • Compiler: vc7/vc8/vc9/vc10/vc11/vc12
  • Threading: single/multi
  • Новые версии

boost

Сборка всех библиотек для конкретного компилятора и конкретной платформы

  • 12 вариантов
  • 3Гб

NuGet-пакет

Отдельный NuGet-пакет для каждой библиотеки boost для всех вариантов конкретного компилятора

  • Доступен из единого репозитория
  • Выбирает подключаемые .lib-файлы
  • Добавляет зависимые пакеты
  • Копирует .dll-файлы в OutDir
  • Прописывает управляющие макросы
  • ≈5Мб
  • Обновляется на новые версии

NuGet Package Manager

Встроенное расширение для Visual Studio

NuGet Package Manager

Настройка репозитория

Репозиторий содержит .nupkg-файлы. Можно использовать даже локальную папку.

Package Sources

Репозиторий

Пакеты доступны на NuGet-сервере (пока тестовый режим)

Установка пакетов

Installing packages

Обновление пакетов

Updating packages

Под капотом

  • В проект добавляется файл packages.config
  • В конец .vcxproj-файла добавляются зависимости от MSBuild-скриптов из подключенных пакетов
  • В SolutionDir добавляется папка packages

packages.config

Файл packages.config

Файл проекта

Файл проекта

Структура пакета

NuGet Package structure

Настройка пакетов

Пакеты настраиваются в свойствах проекта

Referenced Packages Properties

Настройка пакетов

Необходимо продублировать настройки CRT

Referenced Packages Properties

boost auto-link

boost сам подключает необходимые .lib-файлы с помощью директив #pragma в зависимости от ряда макросов

  • CRT: _DEBUG и _DLL, определяются компилятором
  • static/dynamic: BOOST_*_DYN_LINK, определяется пользователем
    • По умолчанию все библиотеки подключаются статически
    • Если определён макрос BOOST_library_DYN_LINK, библиотека library подключается динамически
    • Если определён макрос BOOST_ALL_DYN_LINK, все библиотеки подключаются динамически
    • Если для пакета библиотеки library в свойствах проекта указана опция Linkage=Dynamic (по умолчанию), то определяется макрос BOOST_library_DYN_LINK
    • Нельзя использовать динамическую линковку для статического CRT

Пакет msvcbin

MSVC package

Более подробная информация в Confluence: здесь и здесь

Спасибо за внимание!

Назад к содержанию