вторник, 29 марта 2011 г.

52. Разрешается ли объявлять массив в качестве поля класса. Как присвоить элементам массива начальные значения?

a. Можно, как и обычный локальный (статический или динамический) массив.
б. Только почленно меняя каждый элемент. Однако можно обнулить массив в списке инициализации: array().

Хорошо описано тут

четверг, 24 марта 2011 г.

51. Влияет ли наличие целочисленных констант-полей на размер класса?

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

среда, 23 марта 2011 г.

50. Для чего служит ключевое слово explicit?

Чтобы запретить неявное преобразование типа, выполняемое конструктором. А вообще такой ворос уже был: 48. Как запретить неявное преобразование типа, выполняемое конструктором инициализации?

вторник, 22 марта 2011 г.

49. Какие проблемы могут возникнуть при определении функций преобразования?

Опять повторюсь, все-таки операции преобразования, а не функции.
Можно столкнуться с неоднозначностью (пример спер):
struct Tiny{
  Tiny(int){}
  operator int();
};

int operator+(Tiny, Tiny);

int main() {
  Tiny tiny(1);
  cout << tiny + 2;
}

Компилятор негодует, он не знает то ли преобразовывать первое слагаемое в int (чтобы вызвать обычный operator+(int, int)), то ли второе в Tiny (operator+(Tiny, Tiny)).

Конечно еще существует вероятность случайного неявного преобразования.

Такой оператор следует определить только когда преобразование достаточно очевидно. Но, например, не следует добавлять operator double для вычисления длины вектора, сами же и запутаетесь.

пятница, 18 марта 2011 г.

48. Как запретить неявное преобразование типа, выполняемое конструктором инициализации?

О конструкторе инициализации я писал в ответе на 46-й вопрос.
Для запрета нужно добавить ключевое слово explicit.
class A{
public:
  explicit A(B);
};

void f(A);
Тогда вызовы, типа f(b) (b экземпляр B) будут запрещены. Однако всегда можно воспользоваться явным преобразованием f(A(b)).

47. Для чего нужны функции преобразования? Как объявить такую функцию в классе?

Я уверен, что имеется в виду "оператор преобразования".
Допустим, мы хотим реализовать некоторую цепочку преобразований для объектов классов A, B, C:
ABC
Во-первых, можно написать функции преобразования (ну это понятно).
Во-вторых, использовать конструктор (см. вопрос 46). Для этого нужно добавить конструкторы B::B(A) и C::C(B) и преобразовывать на здоровье. Хоть явно, хоть неявно, как в примере:
struct A{
};

struct B{
  B(const A&){};
};

struct C{
  C(const B&){};
};

int main() {
  A a;
  B b = a;
  C c = b;
}

Я не добавлял никаких операций в класс A, так как B сам преобразовывает AB. Преобразованием BC, занимается C. Но часто оказывается так, что нежелательно или невозможно добавить операцию в C, зато B знает как преобразовать себя в C. Тогда нужно использовать оператор преобразования.
struct A{
};

struct C{
};

struct B{
  B(const A&){};
  operator C(){};
};

int main() {
  A a;
  B b = a;
  C c = b;
}

Итак, мини-шпаргалка:
T → B        используем конструктор B::B(T)
    B → T    используем B::operator T()

пятница, 11 марта 2011 г.

46. Какой вид конструктора фактически является конструктором преобразования типов?

Конструктор с одним параметром. Для чего это нужно? Допустим у класса A есть конструктор принимающий единственный параметр - объект класса B. И есть некоторая функция принимающая объект класса A.
class A{
public:
  A(B);
};

void f(A);
Если у нас есть экземпляр класса B, то мы можем воспользоваться конструктором A(B), создать объект A, и передать в функцию:
B b;
A a(b);
f(a);
Или, если a не нужен для других целей (будет создан временный объект класса A):
B b;
f(A(b));

Но можно еще проще:
B b;
f(b);
Это эквивалентно примеру выше. Компилятор рассуждает так: "Я не могу передать b в f(A), это разные типы, но может быть есть способ преобразовать b к A? Да, можно воспользоваться конструктором A, передав b. Тогда будет создан объект класса A, его и передам в функцию".

Это приведение бывает полезным. Например (Страуструп 11.3.5), для операций с комплексными числами. Вместо такой стопки функций:
complex operator+(complex, complex);
complex operator+(complex, double);
complex operator+(double, complex);
можно оставить всего одну (первую), если в Complex создать конструктор Complex(double)

среда, 2 марта 2011 г.

45. Какие конструкции С++ разрешается использовать в списке инициализации качестве инициализирующих выражений?

Что такое список инициализации? Инициализация членов класса путем явного вызова конструктора.

/* Класс реализующий комплексное число */
class Complex{
...
  Complex(float re, float im);
...
};

class Test{
  Complex a;
public:
  Test():a(3,-2){}
};

Встретил ответ, что используются lvalue, инициированные rvalue. Его нельзя счесть за правильный, разве что сделать кучу оговорок.
int a(1);
++a = 5;
++a это lvalue, так как префиксная операция инкремента возвращает ссылку на объект. Но это не инициализация, и мы не сможем добавить ++a в список инициализации. Но если вы видите, что lvalue инициируется rvalue (int a(1)), то такое можно добавить в список. И можно ли сказать, что a(3,-2) это lvalue инициированное rvalue?

Поэтому, я считаю, правильный ответ: в качестве инициализирующих выражений используются явные вызовы конструкторов.