среда, 6 августа 2025 г.

[prog.c++.thoughts] Design By Contract и приватные методы классов в C++

В C++26 будут включены контракты. Т.е. можно будет сказать, что элементы Design By Contract наконец-то доберутся и до C++.

Недавно в LinkedIn запостил маленький скриншот с примером того, что мне иногда приходится делать посредством комментариев и что будет нормальным образом выражаться в C++26.

Какие-то странные люди, не видевшие исходного кода и ничего не знающие о задаче, для которой данный код был написан, начали говорить мне о плохом дизайне и о том, что для приватных методов контракты и не нужны.

Очень странная, как по мне, точка зрения. Тем более, что как-то ее аргументировать ни один из этих странных комментаторов не удосужился. Поэтому попробую высказать свою точку зрения на данный вопрос.


Технология Design By Contract (DbC) берет свое начало в языке Eiffel (вот еще одно описание от первоисточника). Если вы не знакомы с Eiffel хотя бы в части понимания DbC, то вы, к сожалению, многое упустили в своей профессиональной подготовке. Если же знакомы, то, надеюсь, понимать написанное ниже будет проще.

Ключевыми для нашего разговора будут три вещи (на самом деле в Eiffel есть еще и инварианты для циклов, и утверждения check, похожие на C-шный assert, но их мы спокойно можем игнорировать):

вторник, 5 августа 2025 г.

[prog.c++] Forward declarations для типов хорошо бы держать под контролем и в одном месте

С давних пор с настороженностью отношусь к опережающим объявления (forward declarations) типов в коде. Когда вижу что-то вроде:

class A;
class B;
class C {
  const A * _a;
  B & _b;
  ...
};

то невольно начинаю ждать неприятных приключений. Коих, по моим наблюдениям, бывает два вида.

Во-первых, некоторые компиляторы (не будем показывать пальцем в сторону VC++) различают:

class A;

и

struct A;

Поэтому если в каком-то заголовочном файле есть что-то вроде:

class A;
void f(const A &);

А затем в файле реализации:

struct A { ... };
void f(const A & a) { ... };

то при линковке нужная версия f может и не обнаружиться.

Во-вторых, может оказаться, что где-то осталось:

class A;

Тогда как основное определение A было преобразовано в:

template<typename T>
class SomeTrickyTemplate { ... };

using A = SomeTrickyTemplate<int>;

И мы опять же получим приключения при линковке.


Конечно же хорошо, что все сломается при линковке и нам не придется разбираться с непонятным поведением exe файла, т.к. самого exe файла не будет. Но если у разработчика небольшой опыт в C++, то разобраться с ошибками от линкера ему будет непросто.

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

Но вот когда опережающие объявления без контроля разбросаны по всему проекту, вот это меня сильно напрягает.