Если присваивание и конструктор копирования описаны неявно, то они будут общими функциями-членами и операция присваивания для класса X определяется таким образом, что ее результатом является ссылка типа X& на объект, которому присваивают.
Если в классе X есть функция X::operator=(), параметром которой является сам класс X, то стандартное присваивание не будет порождаться. Если в классе определен какой-либо конструктор копирования, то стандартный конструктор копирования не будет порождаться. Приведем пример:
class X {
//…
public:
X(int);
X(const X&, int = 1);
};
X a(1); // вызов X(int)
X b(a,0); // вызов X(const X&,int)
X c = b; // вызов X(const X&,int)
Присваивание объектов класса X определяется через функцию X::operator=(const X&). Это означает (§R.12.3), что объекты производного класса можно присваивать объектам общего базового класса, например:
class X {
public:
int b;
};
class Y: public X {
public:
int c;
};
void f()
{
X x1;
Y y1;
x1 = y1; // нормально
y1 = x1; // ошибка
}
В этом примере y1.b присваивается x1.b, а x1.c не копируется.
Копирование одного объекта в другой с помощью стандартной операции копирования или стандартного конструктора копирования не изменяет структуру обоих объектов. Приведем пример:
struct s {
virtual f();
//…
};
struct ss: public s {
f();
//…
};
void f()
{
s a;
ss b;
a = b; // на самом деле выполняется a.s::operator=(b)
b = a; // ошибка
a.f(); // вызов s::f
b.f(); // вызов ss::f
(s&)b = a; // присваивание a b
// на самом деле выполняется ((s&)b).s::operator=(a)
b.f(); // все еще вызов ss::f
}
Вызов a.f() приведет к вызову s::f() (как и должно быть для объекта класса s (§R.10.2)), а вызов b.f() приведет к вызову ss::f() (как и должно быть для объекта класса ss).
Говорят, что имя перегружено, если для него задано несколько различных описаний функций в одной области видимости. При использовании имени выбор правильной функции производится путем сопоставления типов формальных параметров с типами фактических параметров, например:
double abs(double);
int abs(int);
abs(1); // вызов abs(int)
abs(1.0); // вызов abs(double)
Поскольку при любом типе T и для самого T, для и T& допустимо одно и то же множество инициализирующих значений, функции, типы параметров которых различаются только использованием, или не использованием ссылки, не могут иметь одинаковые имена, например:
int f(int i)
{
//…
}
int f(int& r) // ошибка: типы функций
{ // недостаточно различны
//…
}
Аналогично, поскольку для любом типе T для самого T, const T и volatile T допустимо одно и то же множество инициализирующих значений, функции, типы параметров которых отличаются только указанной спецификацией, не могут иметь одинаковые имена. Однако, различить const T&, volatile T& и просто T& можно, поэтому допустимы определения функций с одним именем, которые различаются только в указанном отношении. Аналогично, допустимы определения функций с одним именем, типы параметров которых различаются только как типы вида const T*, volatile T* и просто T*.
Не могут иметь одинаковые имена функции, которые отличаются только типом возвращаемого значения.
Не могут иметь одинаковые имена функции-члены, одна из которых статическая, а другая нет (§R.9.4).
С помощью конструкции typedef не создаются новые типы, а только определяется синоним типа (§R.7.1.3), поэтому функции, которые отличаются только за счет использования типов, определенных с помощью typedef, не могут иметь одинаковые имена. Приведем пример:
typedef int Int;
void f(int i) {/*… */}
void f(Int i) {/*… */} // ошибка: переопределение f
С другой стороны все перечисления считаются разными типами, и с их помощью можно различить перегруженные функции, например:
enum E { a };
void f(int i) {/*… */}
void f(E i) {/*… */}
Типы параметров, которые различаются только тем, что в одном используется указатель *, а в другом массив [], считаются идентичными. Напомним, что для типа параметра важны только второй и последующие индексы многомерного массива (§R.8.2.4). Подтвердим сказанное примером:
f(char*);
f(char[]); // идентично f(char*);
f(char[7]); // идентично f(char*);
Читать дальше