Внимание! Преобразования и операторы

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

• Никогда не создавайте взаимных преобразований типов. Другими словами, если класс Foo имеет конструктор, получающий объект класса Bar, не создавайте в классе Bar оператор преобразования для типа Foo.

• Избегайте преобразований во встроенные арифметические типы. Но если преобразование в арифметический тип необходимо, то придется учесть следующее.

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

  - Не создавайте функций преобразования больше, чем в один арифметический тип. Позвольте осуществлять преобразования в другие арифметические типы стандартным функциям преобразования.

Самое простое правило: за исключением явного преобразования в тип bool, избегайте создания функций преобразования и ограничьте неявные конструкторы теми, которые безусловно необходимы.

struct C {

 C(int);

 // другие члены

};

struct D {

 D(int);

 // другие члены

};

void manip(const С&);

void manip(const D&);

manip(10); // ошибка неоднозначности: manip(С(10)) или manip(D(10))

Здесь у структур С и D есть конструкторы, получающие значение типа int. Для версий функции manip() подходит любой конструктор. Следовательно, вызов неоднозначен: он может означать преобразование int в С и вызов первой версии manip() или может означать преобразование int в D и вызов второй версии.

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

manip(С(10)); // ok: вызов manip(const C&)

Необходимость в использовании конструктора или приведения для преобразования аргумента при обращении к перегруженной функции — это признак плохого проекта.

Перегруженные функции и пользовательские преобразования

Если при вызове перегруженной функции два (или больше) пользовательских преобразования обеспечивают подходящее соответствие, они считаются одинаково хорошими. Ранг любых стандартных преобразований, которые могли бы (или не могли) быть обязательными, не рассматриваются. Необходимость встроенного преобразования также рассматривается, только если набор перегруженных версий может быть подобран и использован той же функцией преобразования.

Например, вызов функции manip() был бы неоднозначен, даже если бы один из классов определил конструктор, который требовал бы для аргумента стандартного преобразования:

struct E {

 E(double);

 // другие члены

};

void manip2(const C&);

void manip2(const E&);

// ошибка неоднозначности: применимы два разных пользовательских

// преобразования

manip2(10); // manip2(C(10) или manip2(E(double(10)))

В данном случае у класса С есть преобразование из типа int и у класса E есть преобразование из типа double. Для вызова manip2(10) подходят обе версии функции manip2():

Перейти на страницу:

Похожие книги