Сделай Сам Свою Работу на 5

R.12.8 Копирование объектов класса





 

Объекты класса могут копироваться двумя способами: либо присваиванием (§R.5.17), либо инициализацией (§R.12.1, §R.8.4), которая может происходить при передаче параметров (§R.5.2.2) или результата функции (§R.6.6.3). Для класса X эти две операции концептуально реализуются как операция присваивания и конструктор копирования (§R.12.1). В программе можно определить или одну из них, или обе. Если пользователь не определил их в программе, то они будут для всех членов класса X определяться соответственно как присваивание по членам и инициализация по членам.

Если все базовые классы и все члены класса X имеют конструктор копирования, в котором допустимы в качестве параметра объекты типа const, то порождаемый конструктор копирования для X будет иметь единственный параметр типа const X& и записываться так:

X::X(const X&)

Иначе, у него будет единственный параметр типа X&:

X::X(X&)

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

Аналогично, если все базовые классы и члены класса X имеют операцию присваивания, допускающую параметры типа const, тогда порождаемая для X операция присваивания будет иметь единственный параметр типа const X& и записываться так:



X& X::operator=(const X&)

Иначе, у нее будет единственный параметр типа X&:

X& X::operator=(X&)

и присваивание копированием объектов класса X типа const будет невозможно. Стандартная операция присваивания возвращает ссылку на объект, который нужно было копировать.

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

Присваивание по членам и инициализация по членам означают следующее: если класс X имеет в качестве члена класс M, для реализации присваивания и инициализации члена используются операции присваивания в M и конструктор копирования M соответственно. Если класс имеет член типа const, или член, являющийся ссылкой, или член или базовый класс такого класса, где функция operator=() является частной, то для него стандартная операция присваивания не может быть создана. Аналогично, если член или базовый класс класса M имеет частный конструктор копирования, то стандартный конструктор копирования для такого класса не может быть создан.



Пока не появится необходимость в определении, стандартные присваивание и конструктор копирования будут только описаны (т.е. не будет создано тело функции). Иными словами, функция X::operator=() будет порождена только тогда, когда нет явного описания операций присваивания, а объект класса X присваивается объекту класса X или объекту класса, производного от X, или вычисляется адрес функции X::operator=(). Аналогичная ситуация с инициализацией.

Если присваивание и конструктор копирования описаны неявно, то они будут общими функциями-членами и операция присваивания для класса 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).

 

 

R.13 Перегрузка

 

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

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*);

f(char[9]); // идентично f(char*);

g(char(*)[10]);

g(char[5][10]); // идентично g(char(*)[10]);

g(char[7][10]); // идентично g(char(*)[10]);

g(char(*)[20]); // отлично от g(char(*)[10]);

 

R.13.1 Сопоставление описаний

 

Два описания функций с одинаковыми именами относятся к одной и той же функции, если они находятся в одной области видимости и имеют идентичные типы параметров (§R.13). Функция-член производного класса относится к иной области видимости, чем функция-член базового класса с тем же именем. Рассмотрим пример:

class B {

public:

int f(int);

};

 

class D: public B {

public:

int f(char*);

};

Здесь D::f(char*) скорее скрывает B::f(int), чем перегружает эту функцию.

void h(D* pd)

{

pd-›f(1); // ошибка: D::f(char*) скрывает B::f(int)

pd-›B::f(1); // нормально

pd-›f("Ben"); // нормально, вызов D::f

}

Функция, описанная локально, находится в иной области видимости, чем функция с файловой областью видимости.

int f(char*);

void g()

{

extern f(int);

f("asdf"); // ошибка: f(int) скрывает f(char*) поэтому

// в текущей области видимости нет f(char*)

}

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

class buffer {

private:

char* p;

int size;

 

protected:

buffer(int s, char* store) { size = s; p = store; }

//…

 

public:

buffer(int s) { p = new char[size = s]; }

};

 

 








Не нашли, что искали? Воспользуйтесь поиском по сайту:



©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.