Pull to refresh

Что скрывает class Empty {}

Reading time3 min
Views8.8K
Это заметка о методах, которые C++ создаёт автоматически, даже если вы их не создавали.

Для кого эта заметка? Надеюсь, что она будет интересна начинающим программистам на С++. А опытным программистам позволит лишний раз освежить и систематизировать свои знания.


Итак, если вы написали

class Empty {};

то, знайте, что на самом деле вы создали примерно вот такой класс:

class Empty {
public:
  // Конструктор без параметров
  Empty();
  // Копирующий конструктор
  Empty(const Empty &);
  // Деструктор
  ~Empty();
  // Оператор присвоения
  Empty& operator=(const Empty &);
  // Оператор получения адреса
  Empty * operator&();
  // Оператор получения адреса константного объекта
  const Empty * operator&() const;
};

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

К каким это может привести неприятностям?


Самое неприятное, когда программа работает, но не так как вы хотели. При этом никаких ошибок с точки зрения языка не возникает. Именно к таким неприятностям приводят неявно создаваемые методы.

Пример первый: Конструкторы


Рассмотрим класс, который выдаёт сообщения о создании/удалении объектов и поддерживает статический счётчик объектов (для простоты в виде публичного int).

class CC {
public:
  CC();
  ~CC();
  static int cnt;
};

Реализация тривиальна:

int CC::cnt(0);
CC::CC()  { cnt++; cout << "create\n";}
CC::~CC() { cnt--; cout << "destroy\n";}

Что будет делать такая программа?

void f(CC o) {}
int main() {
  CC o;
  cout << " cnt = " << o.cnt << "\n";
  f(o);
  cout << " cnt = " << o.cnt << "\n";
  f(o);
  cout << " cnt = " << o.cnt << "\n";
  return 0;
}

Результат может удивить неподготовленного читателя:

create
 cnt = 1
destroy
 cnt = 0
destroy
 cnt = -1
destroy

Складывается ощущение, что объект был создан только один раз, а удалён — трижды. Счётчик объектов уходит в минус. При этом программа спокойно отрабатывает и нигде не валится.

Как вы понимаете, это произошло потому, что мы не учли автоматически созданный конструктор копирования, который только копирует, но ничего не печатает и не корректирует счётчик.

Мы можем исправить эту ситуацию, если сами допишем конструктор копирования

CC::CC(const CC &) {
  cnt++; cout << "create (copy)\n";
}

Теперь мы получим абсолютно разумный результат:

create
 cnt = 1
create (copy)
destroy
 cnt = 1
create (copy)
destroy
 cnt = 1
destroy

Аналогичные засады возникают при присвоении (operator=); но...

Пример второй: Получение адреса


… самые, пожалуй, изысканные подвохи могут возникать, если вы реализовали нетривиальный метод получения адреса

CC * operator&();

но забыли реализовать его двойник, обладающий теми же (или иными?) нетривиальными свойствами:

const CC * operator&() const;

Пока ваша программа ограничивается не-константными объектами:

СС o;
CC *p;
p = &o;

всё работает. Это может продолжаться очень долго, все уже позабудут, как устроен объект CC, проникнутся к нему доверием и не буду думать на него при появлении ошибок.

Но рано или поздно появится код:

CC const o;
CC const *q = &o;

И метод

CC * operator&();

Предательски не сработает (про перегрузку по const я уже писал вот тут).

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

От этих недоразумений очень легко застраховаться!


Самый простой способ — создать прототипы всех методов, создаваемых автоматически и не создавать реализации.

Тогда программа просто не слинкуется и вы получите вполне разумное сообщение. Я получил такое:

/var/tmp//ccGQszLd.o(.text+0x314): In function `main':
: undefined reference to `CC::operator&() const'

Ваш компилятор может выразиться чуть-чуть иначе.

Если этот способ кажется вам корявым (у вас есть на то все основания и я с вами солидарен), то можно создать «полноценные» методы, но сделать их приватными.

Тогда вы тоже получите сообщения об ошибках ещё на этапе компиляции.

count2.cpp: In function 'int main()':
count2.cpp:22: error: 'const CC* CC::operator&() const' is private
count2.cpp:37: error: within this context

Согласитесь, что это сообщение выглядит как-то… поприличней.

Ну и третий способ (последний в списке, но не последний по значению) — просто честно реализовать все необходимые методы, не откладывая это дело в долгий ящик :-)
Tags:
Hubs:
+51
Comments65

Articles