Re[4]: const-корректность методов
От: Roman Odaisky Украина  
Дата: 07.01.09 10:36
Оценка: 12 (3)
Здравствуйте, LightGreen, Вы писали:

LG>Предположим, что у класса A есть контейнеры x,y, по которым константный метод calculate() вычисляет какие-то значения z. И заливает результаты в другой контейнер, переданный по ссылке. Смысл в том, что calculate() исходит из того, что класс в ходе вычислений не меняется (потому что сам метод определён как const). Но если в каком-то другом методе A::foo() разработчик решил позвать process() — и передал ему член самого класса A в качестве выходного параметра z, то получается накладка. Метод calculate начнёт писать в собственный instance и компилятор этого не заметит. Понятно, что это недостаток дизайна. Но компилятор мог бы и поругаться.


Это называется aliasing, известная и довольно сложная проблема.

Во-первых, можно обстрелять свои ноги, если по невнимательности передать два указателя на одно и то же в функцию, которая ожидает независимые значения. Простейший пример — std::copy(first, last, first + 1).

Во-вторых, даже если программист аккуратен в этом отношении, компилятор всё равно должен будет принимать предосторожности, которые вредят оптимизации. Например:
std::vector<std::ostream *> files;
populate(files);
for(size_t i = 0; i < files.size(); ++i)
{
    files[i]->do_something();
}

Типичная реализация std::vector представляет собой три указателя: на первый элемент, на элемент «после последнего», и на элемент сразу после выделенной области. Т. е., size_type size() const { return last — first; } и size_type capacity() const { return end_of_storage — first; }. В вышеприведенном примере компилятор обязан вычислять size каждый раз, потому что содержащиеся в векторе указатели потенциально могут указывать на область памяти, в которой находятся значения first или last, и do_something может их перезаписывать (что, если files[0] == (std::ostream *)&files.__last?). Хотя казалось бы, что можно один раз вычислить size и потом сравнивать i с этой константой.

Или что-нибудь вроде перемножения матриц, что на C может выглядеть так: void multiply_matrices(size_t const n, int const source_left[][], int const source_right[][], int destination[][]). Оптимизатору приходится предполагать, что каждая запись в память через destination может повлиять на одну из исходных матриц, что сильно мешает ему кешировать данные.

Для решения этих проблем в C99 было добавлено правило «strict aliasing», запрещающее писать в память через указатели измененного типа, и ключевое слово restrict. Например:
void strict_aliasing_example(int* i, double* d)
{
    int const i_before = *i;
    *d = 0.5772156649;
    int const i_after = *i;
    assert(i_before == i_after);
}

void restrict_example(int* restrict i, int* restrict j)
{
    int const i_before = *i;
    *j = 42;
    int const i_after = *i;
    assert(i_before == i_after);
}

void possible_aliasing(int* i, int* j)
{
    int const i_before = *i;
    *j = 42;
    int const i_after = *i;
    printf("i was %d and it could have changed. Now i = %d.\n", i_before, i_after);
}

В первом случае оптимизатор может выкинуть второе чтение из памяти, потому что i и d разных типов и, следовательно, должны указывать на разные участки памяти (иначе UB). Во втором случае тип один и тот же, но явно указано restrict. В третьем случае приходится читать *i два раза, потому что запись по адресу j может изменять значение по адресу i.

P. S. Надо бы раскрыть эту тему в вики.
До последнего не верил в пирамиду Лебедева.
Re: const-корректность методов
От: Кодт Россия  
Дата: 08.01.09 11:34
Оценка: 1 (1) +1
Здравствуйте, LightGreen, Вы писали:

LG>Код без проблем компилируется и выполняется (VC++ 8.0).

LG>Интересно, что это — недоработка компилятора или приоритет non-const int& и A& над const-модификатором метода?

Эта штука называется aliasing — когда к одному и тому же объекту можно доступиться под двумя разными именами/указателями/ссылками.

Модификатор доступа const не является блокировкой объекта в рантайме (мол, как только мы зашли в const-метод, все остальные тоже не смейте модифицировать этот объект под каким бы то ни было алиасом).
Если такая блокировка нужна, то требуется прибегнуть к каким-то рукоделиям.

Алиасинг нужно учитывать, и не только в данном случае.
Чаще встречается ситуация, обратная твоей
class Foo
{
    char *str;
    
    .....
    
    void assign(Foo const& other)
    {
        // код, не учитывающий алиасинг, приведёт к стрельбе по памяти
        free(str);
        str = strdup(other.str);
    }
    Foo& operator=(Foo const& other) { assign(other); }
};

int main()
{
    Foo a("hello");
    a = a;
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re: const-корректность методов
От: Alexander G Украина  
Дата: 06.01.09 21:17
Оценка: 9 (1)
Здравствуйте, LightGreen, Вы писали:


  int x;
  void modify( A& a ) const
  {
     a.x++;
  }

— этот const относится к this только


  int x;
  void modify( A const& a )
  {
     x++;
  }

— этот const относится к a только.


  mutable int x;
  void modify( A const& a ) const
  {
    ++x;
    ::a = ::SomeFunction();
  }

Даже эти два const не гарантируют полную чистоту, т.к. есть mutable члены, есть глобальные переменные, есть глобальные функции...
Русский военный корабль идёт ко дну!
Re[4]: const-корректность методов
От: Erop Россия  
Дата: 07.01.09 14:01
Оценка: 1 (1)
Здравствуйте, LightGreen, Вы писали:

LG>Метод calculate начнёт писать в собственный instance и компилятор этого не заметит. Понятно, что это недостаток дизайна. Но компилятор мог бы и поругаться.


IMHO, компилятор тут не при чём
Автор: Erop
Дата: 04.09.07
. Это вопрос к программисту...
Другое дело оптимизация...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: const-корректность методов
От: FoolS.Top Армения  
Дата: 06.01.09 22:10
Оценка: +1
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, LightGreen, Вы писали:


AG>Смысл в том, что calculate() исходит из того, что класс в ходе вычислений не меняется


Одно маленьнкое уточнение, не меняется не КЛАСС, а об'ект данного класса.
Feierlich, misterioso
Re[5]: const-корректность методов
От: Roman Odaisky Украина  
Дата: 07.01.09 14:36
Оценка: +1
Здравствуйте, Erop, Вы писали:

E>IMHO, компилятор тут не при чём
Автор: Erop
Дата: 04.09.07
.


Я тут собираю хорошие сообщения, чтобы их занести в новосозданную вики на RSDN. Даешь ли ты торжественное официальное согласие перенести вышеупомянутое сообщение туда на условиях неведомо какой
Автор: anonymous
Дата: 10.12.08
лицензии?
До последнего не верил в пирамиду Лебедева.
const-корректность методов
От: LightGreen  
Дата: 06.01.09 20:24
Оценка:
Пример упрощённый, взятый из реального проекта.
class A
{
  int a;
  bool state;
  void set(int& x) const
  {
     x = 1;
  }
  void modify( A& a ) const
  {
     a.x++;
  }
  void hack()
  {
     set(a);
     modify(*this);
  }
};

Код без проблем компилируется и выполняется (VC++ 8.0).
Интересно, что это — недоработка компилятора или приоритет non-const int& и A&
над const-модификатором метода?
Re: const-корректность методов
От: FoolS.Top Армения  
Дата: 06.01.09 21:03
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Пример упрощённый, взятый из реального проекта.

LG>
LG>class A
LG>{
LG>  int a;
LG>  bool state;
LG>  void set(int& x) const
LG>  {
LG>     x = 1;
LG>  }
LG>  void modify( A& a ) const
LG>  {
LG>     a.x++;
LG>  }
LG>  void hack()
LG>  {
LG>     set(a);
LG>     modify(*this);
LG>  }
LG>};
LG>

LG>Код без проблем компилируется и выполняется (VC++ 8.0).
LG>Интересно, что это — недоработка компилятора или приоритет non-const int& и A&
LG>над const-модификатором метода?

Я не понял, кто здесь x? У меня дает error C2039: 'x' : is not a member of 'A' (VS 2008).
Feierlich, misterioso
Re[2]: const-корректность методов
От: LightGreen  
Дата: 06.01.09 21:07
Оценка:
Здравствуйте, FoolS.Top, Вы писали:

FT>Я не понял, кто здесь x? У меня дает error C2039: 'x' : is not a member of 'A' (VS 2008).


Упс.. в этой строке опечатка, должно быть
a.a++;

Копипастил с изменениями, поэтому переменные не самым лучшим образом подобраны, но суть
примера остаётся.
Re: const-корректность методов
От: landerhigh Пират  
Дата: 06.01.09 21:13
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Пример упрощённый, взятый из реального проекта.

Выстрел в ногу, не более того
LG>
LG>class A
LG>{
LG>  int a;
LG>  void modify(const A& a ) const
LG>  {
LG>     a.a++;
LG>  }
LG>};
LG>

А если так?
www.blinnov.com
Re[2]: const-корректность методов
От: sgenie  
Дата: 06.01.09 21:14
Оценка:
А в чем проблема-то? То, что в const-метод передается volatile параметр, которыи можно менять? Это, конечно, можно отловить, но надо ли? Для отловки надо знать типизацию параметра — а если передается обьект наследованого типа, где изменения разрешены?

Здравствуйте, FoolS.Top, Вы писали:

FT>Здравствуйте, LightGreen, Вы писали:


LG>>Пример упрощённый, взятый из реального проекта.

LG>>
LG>>class A
LG>>{
LG>>  int a;
LG>>  bool state;
LG>>  void set(int& x) const
LG>>  {
LG>>     x = 1;
LG>>  }
LG>>  void modify( A& a ) const
LG>>  {
LG>>     a.x++;
LG>>  }
LG>>  void hack()
LG>>  {
LG>>     set(a);
LG>>     modify(*this);
LG>>  }
LG>>};
LG>>

LG>>Код без проблем компилируется и выполняется (VC++ 8.0).
LG>>Интересно, что это — недоработка компилятора или приоритет non-const int& и A&
LG>>над const-модификатором метода?

FT>Я не понял, кто здесь x? У меня дает error C2039: 'x' : is not a member of 'A' (VS 2008).
Re[2]: const-корректность методов
От: FoolS.Top Армения  
Дата: 06.01.09 21:27
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, LightGreen, Вы писали:



AG>
AG>  int x;
AG>  void modify( A& a ) const
AG>  {
AG>     a.x++;
AG>  }
AG>

AG>- этот const относится к this только


AG>
AG>  int x;
AG>  void modify( A const& a )
AG>  {
AG>     x++;
AG>  }
AG>

AG>- этот const относится к a только.


AG>
AG>  mutable int x;
AG>  void modify( A const& a ) const
AG>  {
AG>    ++x;
AG>    ::a = ::SomeFunction();
AG>  }
AG>

AG>Даже эти два const не гарантируют полную чистоту, т.к. есть mutable члены, есть глобальные переменные, есть глобальные функции...

Согласен с Вами. А& значит передаваемый обЬект типа А, а не сам обЬект, так что все правильно. const относится кто тому-же обЬекту...
Feierlich, misterioso
Re[2]: const-корректность методов
От: LightGreen  
Дата: 06.01.09 21:32
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>
AG>  int x;
AG>  void modify( A& a ) const
AG>  {
AG>     a.x++;
AG>  }
AG>

AG>- этот const относится к this только

Согласен. Другое дело — что понимать под this. В вызывающем методе тот же самый this
и в моём примере это даже на этапе компиляции известно. Собственно, вопрос был в том,
является ли это ошибкой компилятора. Уже понял, что не является.
Спасибо за развёрнутый ответ.
Re[3]: const-корректность методов
От: LightGreen  
Дата: 06.01.09 21:40
Оценка:
Ещё несколько мыслей по этой теме.
Предположим, что у класса A есть контейнеры x,y, по которым константный метод calculate() вычисляет какие-то значения z. И заливает результаты в другой контейнер, переданный по ссылке. Смысл в том, что calculate() исходит из того, что класс в ходе вычислений не меняется (потому что сам метод определён как const). Но если в каком-то другом методе A::foo() разработчик решил позвать process() — и передал ему член самого класса A в качестве выходного параметра z, то получается накладка. Метод calculate начнёт писать в собственный instance и компилятор этого не заметит. Понятно, что это недостаток дизайна. Но компилятор мог бы и поругаться.
Re[4]: const-корректность методов
От: FoolS.Top Армения  
Дата: 06.01.09 21:54
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Ещё несколько мыслей по этой теме.

LG>Предположим, что у класса A есть контейнеры x,y, по которым константный метод calculate() вычисляет какие-то значения z. И заливает результаты в другой контейнер, переданный по ссылке. Смысл в том, что calculate() исходит из того, что класс в ходе вычислений не меняется (потому что сам метод определён как const). Но если в каком-то другом методе A::foo() разработчик решил позвать process() — и передал ему член самого класса A в качестве выходного параметра z, то получается накладка. Метод calculate начнёт писать в собственный instance и компилятор этого не заметит. Понятно, что это недостаток дизайна. Но компилятор мог бы и поругаться.

Очень хороший пример. Я думаю, что компилятор не может так "глубоко копать". Во всех случаях const означает, что непосредственно сам об'ект не может изменяться. "Я так думаю".
Feierlich, misterioso
Re[4]: const-корректность методов
От: Alexander G Украина  
Дата: 06.01.09 22:00
Оценка:
Здравствуйте, LightGreen, Вы писали:

Смысл в том, что calculate() исходит из того, что класс в ходе вычислений не меняется (потому что сам метод определён как const). Но если в каком-то другом методе A::foo() разработчик решил позвать process() — и передал ему член самого класса A в качестве выходного параметра z, то получается накладка. Метод calculate начнёт писать в собственный instance и компилятор этого не заметит.
Допустим struct A { calculate(A&) const; }


В случае
A a;
a.calculate(a);

нет константности a.

В случае

A a;
A b;
a.calculate(b);


и если изменения b и глобальные вызовы не меняют a, изменить a можно будет:
1. Изменив член через mutable
2. Изменив член через const_cast или С-style cast
3. Вызвав не-костантный метод через const_cast или С-style cast
1 осознанное ограничение const корректности, 2 и 3 осознанное нарушение const корректности.

Однако, константность не глубокая, поэтому нет защиты от подобного:
struct A 
{ 
    int i;
    A * that;
    A() { i = 0; that = this; }
    void calculate(void) const { ++that->i; }
};
Русский военный корабль идёт ко дну!
Re: const-корректность методов
От: rg45 СССР  
Дата: 07.01.09 00:38
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Пример упрощённый, взятый из реального проекта.

LG>
LG>class A
LG>{
LG>  int a;
LG>  bool state;
LG>  void set(int& x) const
LG>  {
LG>     x = 1;
LG>  }
LG>  void modify( A& a ) const
LG>  {
LG>     a.x++;
LG>  }
LG>  void hack()
LG>  {
LG>     set(a);
LG>     modify(*this);
LG>  }
LG>};
LG>

LG>Код без проблем компилируется и выполняется (VC++ 8.0).
LG>Интересно, что это — недоработка компилятора или приоритет non-const int& и A&
LG>над const-модификатором метода?

А что здесь некорректного? В теле метода modify присутствуют две ссылки — одна константная (*this), вторая неконстантная (a). Модификация объекта происходит именно через неконстантную ссылку, а в том, что обе эти ссылки ссылаются на один объект, никакого нарушения константности нет. То же можно сказать о методе set.

Наверное, приведенный пример может сбивать с толку, если забыть о том, что у всех нестатических функций-членов неявно присутствует дополнительный параметр — указатель на объект, для которого вызывается функция-член. Модификатор const, который указывается в объявлении функции члена относится именно к этому неявному параметру и ни к какому другому. Если модифицировать код так, чтоб передача указателя на объект стала явной, то иллюзия нарушения константности исчезает (как мне кажется):
struct A
{
  int a;
  bool state;
};
void set(const A* this_, int& x) 
{ 
  x = 1; //модификация по неконстантной ссылке
  this_->a = 1; //error
}
void modify(const A* this_, A& a)
{
  a.a++; //модификация по неконстантной ссылке
  this_->a++; //error
}
void i_nikakoi_ne_hack(A* this_)
{
  //В обоих нижеследующих вызовах по неконстантному указателю 
  //образуются константный указатель и неконстнатная ссылка.
  //Все законно.  
  set(this_, this_->a);
  modify(this_, *this_); 
}
--
Справедливость выше закона. А человечность выше справедливости.
Re[5]: const-корректность методов
От: Alexander G Украина  
Дата: 07.01.09 10:58
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Для решения этих проблем в C99 было добавлено правило «strict aliasing», запрещающее писать в память через указатели измененного типа, и ключевое слово restrict. Например:

RO>
RO>void strict_aliasing_example(int* i, double* d)
RO>{
RO>    int const i_before = *i;
RO>    *d = 0.5772156649;
RO>    int const i_after = *i;
RO>    assert(i_before == i_after);
RO>}

RO>void restrict_example(int* restrict i, int* restrict j)
RO>{
RO>    int const i_before = *i;
RO>    *j = 42;
RO>    int const i_after = *i;
RO>    assert(i_before == i_after);
RO>}

RO>void possible_aliasing(int* i, int* j)
RO>{
RO>    int const i_before = *i;
RO>    *j = 42;
RO>    int const i_after = *i;
RO>    printf("i was %d and it could have changed. Now i = %d.\n", i_before, i_after);
RO>}
RO>

RO>В первом случае оптимизатор может выкинуть второе чтение из памяти, потому что i и d разных типов и, следовательно, должны указывать на разные участки памяти (иначе UB). Во втором случае тип один и тот же, но явно указано restrict. В третьем случае приходится читать *i два раза, потому что запись по адресу j может изменять значение по адресу i.

Я бы добавил (поправь если ошибаюсь), что примеры strict_aliasing_example и
possible_aliasing относятся также к С++, а restrict_example — не относится к С++. И ещё такой случай:

void possible_aliasing_with_different_types(int* i, char unsigned* j)
{
    int const i_before = *i;
    *j = 42;
    int const i_after = *i;
    printf("i was %d and it could have changed. Now i = %d.\n", i_before, i_after);
}


RO>P. S. Надо бы раскрыть эту тему в вики.

Вики таки работает ?
Русский военный корабль идёт ко дну!
Re[6]: const-корректность методов
От: Erop Россия  
Дата: 09.01.09 03:34
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Здравствуйте, Erop, Вы писали:


E>>IMHO, компилятор тут не при чём
Автор: Erop
Дата: 04.09.07
.


RO>Я тут собираю хорошие сообщения, чтобы их занести в новосозданную вики на RSDN. Даешь ли ты торжественное официальное согласие перенести вышеупомянутое сообщение туда на условиях неведомо какой
Автор: anonymous
Дата: 10.12.08
лицензии?


Даю. Я что-то должен для этого сделать?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[7]: const-корректность методов
От: Roman Odaisky Украина  
Дата: 09.01.09 08:43
Оценка:
Здравствуйте, Erop, Вы писали:

RO>>Я тут собираю хорошие сообщения, чтобы их занести в новосозданную вики на RSDN. Даешь ли ты торжественное официальное согласие перенести вышеупомянутое сообщение туда на условиях неведомо какой
Автор: anonymous
Дата: 10.12.08
лицензии?


E>Даю. Я что-то должен для этого сделать? :)


Если у тебя много лишнего времени, мог бы написать что-нибудь интересное для вики, а то там пока народу мало.
До последнего не верил в пирамиду Лебедева.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.