среда, 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()