среда, 31 августа 2011 г.

Пример работы InterlockedExchange

long g_sum;

unsigned __stdcall ThreadFunc(void* argList){
  for(int i = 0; i<100000000; i++){
    g_sum++;
  }
  return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
  /* Запускаем 2 потока с одной и той же функцией */
  HANDLE hThreads[2];
  hThreads[0] = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, NULL, 0, NULL);
  hThreads[1] = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, NULL, 0, NULL);

  /* Ждем... */
  WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);

  _tprintf(TEXT("sum: %d\n"), g_sum);

  CloseHandle(hThreads[0]);
  CloseHandle(hThreads[1]);
  return 0;
}

Скорее всего, результат будет всегда разный, между 100 и 200 миллионами (можно увеличить число операций). Если бы каждый поток блокировал g_sum от другого потока на время изменения, то мы бы получили ровно 200 миллионов.

Самое простое, что можно сделать для этого - заменить g_sum++ на InterlockedExchangeAdd(&g_sum, 1). Теперь результат всегда корректен. Блокировка происходит на уровне процессора.

Почему обычный инкремент работает некорректно?

g_sum++ эквивалентно:
  mov eax, g_sum;
  add eax, 1;
  mov g_sum, eax;

То есть, реально это несколько операций. И поток может быть прерван другим потоком после выполнения любой из них. Получается какая-то каша.

пятница, 8 апреля 2011 г.

55. Как объявить в классе и проинициализировать статический константный массив?

Как и другие статические константы
class A{
public:
  static const int arr[];
};

const int A::arr[] = {0,1,2,3,4};

54. Для чего нужны статические поля в классе? Как они определяются?

Обычно, каждому объекту соответствуют собственные значения всех его полей. Также к полям класса относят статические поля (static data members, static class fields, class variables) — поля, общие для всех объектов класса.

Статические поля семантически не отличаются от обычных глобальных переменных, но они доступны только по квалифицированному имени (то есть с указанием имени класса), и поэтому, в отличие от глобальных переменных, не загромождают пространство глобальных имён.
wiki

class A{
public:
  static int i;
}

/* Обязательно нужно проинициализировать */
int A::i = 0;

int main(){
  A::i = 1;
}

суббота, 2 апреля 2011 г.

53. Сколько операндов имеет операция индексирования []? Какой вид результата должна возвращать эта операция?

a. Ровно один операнд (параметр).
б. С точки зрения компилятора ничего она не должна и может возвращать что угодно. Или вообще не возвращать. Однако, я бы обиделся на std::vector, если бы не смог присваивать значения таким образом:
std::vector a(10);
a[3] = 42;

Поэтому, крайне желательно возвращать некоторое lvalue по ссылке, например:
double& operator[](int i){return _lvalue_;}

вторник, 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?

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

воскресенье, 20 февраля 2011 г.

44. В каком порядке инициализируются поля в классе? Совпадает ли этот порядок с порядком перечисления инициализаторов в списке инициализации конструктора?

struct A1{
  A1(){cout<<1;}
};

struct A2{
  A2(){cout<<2;}
};

class B{
  A1 x;
  A2 y;
public:
  B():y(),x(){}
};

int main() {
  B b;
}

Сначала объявлен член x, затем y. А в списке инициализации обратный порядок. Посмотрим что важнее. Запускаем. Получим "12". Все верно, ведь инициализация происходит в порядке объявления их в классе, а не в списке инициализации (так написано в спецификации).

А еще MinGW предупреждает:
In constructor 'B::B()':
|18|warning: 'B::y' will be initialized after
|17|warning: 'A1 B::x'
|20|warning: when initialized here


y будет проинициализирован позже, чем x, несмотря на порядок в списке инициализации.

пятница, 18 февраля 2011 г.

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

Я уже насписал в ответе на "42. Как объявить константу в классе? Можно ли объявить дробную константу?". Вкратце:
class A{
  const T i;
  static const T i1 = 1; /* Если T - неинтегральный тип, может не сработать */ 
  static const T i2;  /* Для неинтегральных типов здесь только объявить, а проинициализировать позже */
  A():i(const_value){}
};

const T A::i2 = const_value;

К интегральным типам относятся char, short, int и long.

Есть еще один способ создания констант в классе, это enum hack. Изменим пример из ответа на 42-й вопрос.
class Year {
  /*...*/
  enum {MIN_YEAR = 1900, MAX_YEAR = 2100};
  /*...*/
}

четверг, 17 февраля 2011 г.

42. Как объявить константу в классе? Можно ли объявить дробную константу?

Сначала нужно подумать, что может означать "константа в классе". Попробую использовать относительно реальные примеры, а не мой любимый "class A" :)

Первый случай

Допустим, мы пишем класс Polygon (многоугольник). Мы хотим, чтобы количество вершин задавалось при создании объекта.
class Polygon{
  int vertexes;
public:
  Polygon(int v):vertexes(v){}
}
Таким образом, при создании экземпляра будет необходимо задать количество вершин, так как конструктора по умолчанию уже нет. Все хорошо, но в какой-нибудь функции-члене можно ненароком изменить vertexes, а мы этого не хотим. Тогда и добавляем модификатор const.
const int vertexes;
Значение такой переменной можно задать только в списке инициализации конструктора, как и сделано в примере. А вот так даже и не пытайтесь:
Polygon(int v){vertexes = v;} /*ошибка*/
см. также Страуструп 10.4.6.1. "Необходимая инициализация членов"

Второй случай

У нас есть класс Year, и мы хотим, чтобы задаваемый год не выходил за границы диапазона
const int MIN_YEAR = 1900;
const int MAX_YEAR = 2100;
const int DEFAULT_YEAR = 2011;

class Year{
  int year;
public:
  Year(int y);
};

Year::Year(int y){
  if(y < MIN_YEAR || y > MAX_YEAR){
    y = DEFAULT_YEAR;
  }else{
    year = y;
  }
}
Хорошо было бы, если класс не молчал, а как-то предупреждал о выходе за пределы, например генерировал исключение, но это пока не важно. Я хочу поместить эти константы в класс. Способ из предыдущего примера явно не подходит. Тут явно напрашивается связь со статическими членами. Так и есть, можно использовать статические константные члены.
class Year{
  int year;
  static const int MIN_YEAR = 1900;
  static const int MAX_YEAR = 2100;
  static const int DEFAULT_YEAR;
public:
  Year(int y);
};

const int Year::DEFAULT_YEAR = 2011;

Year::Year(int y){
  if(y < MIN_YEAR || y > MAX_YEAR){
    year = DEFAULT_YEAR;
  }else{
    year = y;
  }
}
Обратили внимание, что DEFAULT_YEAR проинициализирован за пределами класса? Да, можно и так. А с дробными, согласно спецификации, только так.

Можно ли объявить дробную константу?

Вопрос относится ко второму случаю, с первым (класс Polygon) все нормально.
Дробную объявить можно, но проинициализировать только аналогично способу инициализации DEFAULT_YEAR. Однако в спецификации нет явного запрета на:
class A{
  static const double PI  = 3.1415;
}
MinGW на это не ругается, VS не знаю. Почему так сложно с дробными? Я и сам не понял, можно почитать ветку форума, там вроде все выяснили.

вторник, 15 февраля 2011 г.

Сервис Connecting Bloggers

Мы решили сделать сервис, которые делает тоже самое, автоматически, на основе информации из ваших блогов — мы берем ваши посты, выбираем картинки к ним (если картинки нет, то пытаемся найти в интернете подходящую) и генерируем информер, который вставляется в блог.
http://habrahabr.ru/blogs/blogosphere/113613/
Ради любопытства прикрутил виджет, его можно видеть справа. Автоматически подбирающиеся картинки, конечно не в тему, но иногда бывают забавными, пусть пока побудут.

понедельник, 14 февраля 2011 г.

Страуструп "Язык программирования C++". Специальное издание 2011.

Забывал написать. Наконец-то переиздали. Заказал в декабре, получил 31-го, прямо под новый год.
На Озоне аж 924 рубля.
На books.ru - 622.
А я брал здесь за 684. Удобно, что посылку можно забрать в пункте выдачи заказов и там же оплатить по ее прибытии.

воскресенье, 23 января 2011 г.

41. Как проинициализировать динамическую переменную?

С помощью оператора new, а также его разновидностей.

Динамическая переменная может инициализироваться следующими способами:
1.
class A {/* определение класса */};
A* pa = new A(); /*Какой-либо конструктор.*/
delete pa; /*Не забываем освободить память*/
Динамическим может быть не только объект:
int* pi = new int(1);
cout << *pi;
delete pi;

2. placement new. Не выделяет память, а размещает объект в заранее определённом месте.
int i = 1;
int* j = new(&i) int(2); /*Разместили по томо же адресу, что и i.*/
cout << i << "\n"; /*Значение i затерто*/

3. nothrow new. В случае нехватки памяти, при вызове обычного new, им генерируется исключение std::bad_alloc. А new(nothrow) просто возвращает 0.

Пробежался по верхам, так как о new еще будут вопросы. А пока задам вопрос случайно заглянувшему читателю: бывает ли nothrow версия оператора placement new и почему?

суббота, 8 января 2011 г.

40. Приведите несколько случаев, когда конструктор вызывается неявно.

Приведение типа, передача по значению, копирование, конструирование внутренних объектов. Конструктор внутренних объектов вызывает сгенерированный конструктор по умолчанию, решил в примере не отражать, и так понятно :)
class A{
  int x;
public:
  A(){};
  A(int p):x(p){};
  A(A const& a){x = a.x;}
};

A f(A a){
  return 3; //это return A(3); пример 3.
}

A g(A a){ 
  return a; //Вызов конструктора копирования. пример 5.
}

int main(){
  A a;
  a = 1; /*это a = A(1); пример 1.*/
  f(2); /*это f(A(2)); пример 2.*/
  g(a); /*Вызов конструктора копирования. пример 4.*/
}

39. Может ли конструктор быть приватным? Какие последствия влечет за собой объявление конструктора приватным?

Может. Конструктор нельзя будет вызвать (кроме как из члена этого класса), а значит создать объект будет невозможно, если не предусмотреть способ вызова конструктора из члена класса. Примечание: если мы объявили какой-нибудь конструктор, даже приватный, то конструктор по умолчанию создан не будет.
Используется, например, в реализациях паттерна singleton

38. Сколько конструкторов может быть в классе? Допускается ли перегрузка конструкторов? Какие виды конструкторов создаются по умолчанию?

a. С разными параметрами сколь угодно.
b. Да. Они подчиняются тем же правилам разрешения перегрузок, что и обычные методы.
c. Конструктор по умолчанию (без параметров), конструктор копирования.

37. Дайте определение конструктора. Каково назначение конструктора? Перечислите отличия конструктора от метода.

Ответ вкратце, благо информация по этому вопросу есть в любой книге.
a, b. Особый метод предназначенный для инициализации объектов класса.
c. Конструктор не возвращает значение, не наследуется