2014-01-13 5 views
1
class Test 
{ 
public: 
    Test() : i(0), ptr(&i) {} 
    int i; 
    int *ptr; 
    void change_const (int x) const { *ptr=x; } 
}; 

int main() 
{ 
    const Test obj; 
    obj.ptr = &obj.i; // error 
    obj.change_const(99); 
    return 0; 
} 

Хотя в objptr имеет типа int *const, конструктор может заставить его указать на i типа const int. Явная попытка сделать это, конечно, терпит неудачу. Почему конструктор предлагает эту уязвимость относительно корректности const? Другие неявно очевидные уязвимости, такие каксопз корректность с константными объектами и указателями членов, конструктор уязвимость

int *ptr; 
const int **c_ptr = &ptr; // error 
const int c = 10; 
*c_ptr = &c; 
*ptr = 20; // because here change of c possible 

также хорошо известны.

+0

C++ иногда дает вам достаточно веревки, чтобы повесить себя. Просто не пишите код собачки в первую очередь и играйте по правилам –

ответ

2

Очевидно, что конструктор (или, по крайней мере, список инициализаторов, если не тело ctor) должен иметь возможность записать значение в i.

Как это происходит, способ, которым C++ достигает этого, - сделать this указателем на не-const в конструкторе (и деструкторе). В принципе, const -ness obj не начинается, пока конструктор не завершит выполнение. Вот почему уязвимость существует из-за простого, но несовершенного решения технической проблемы создания const -qualified объектов.

Возможно, в принципе это могло бы быть сделано по-другому. Я предполагаю, что вам понадобится отдельная версия конструктора конструктора, в котором компилятор применяет разные правила (так же, как обычные функции-члены могут быть const), обрабатывая данные как const и, следовательно, (1) позволяя им инициализировать, но не назначать, (2), запрещающий инициализацию ptr от &i, поскольку последний имел бы тип int const*. C++ не делает этого, и в результате у него есть эта лазейка, через которую вы проехали. Если бы это было сделано, у людей было бы больше проблем с написанием конструкторов в определенных случаях, так что это компромисс с дизайном.

Знайте, что аналогичным образом volatile -qualified объект не является volatile в собственном конструкторе или деструкторе.

+0

Да, я думал об этих возможностях отдельной конструкторской конструкции 'const', но я думаю, что теоретически было бы лучше всего иметь только что-то вроде вашей точки (2): изменение значения оператора' & 'для возврата 'const * type' переменной (member), имеющей' type'. Что касается точки (1), 'this' должен действительно оставаться непостоянным до тех пор, пока конструктор не закончит, чтобы разрешить сложные задания, такие как jogojapan (петли, ...). Таким образом, только другое значение '&' внутри дополнительного конструктора 'const' может остановить эту уязвимость? Может быть? – mb84

+0

@ mb84: Хм, вы должны быть уверены, что значение '&' является необходимым и достаточным. Если вы хотите разрешить 'i = 1', тогда люди ожидают, что' init (& i) 'будет в порядке с void init (int * p) {* p = 1; } '. Вы заставили бы их использовать 'const_cast' в этом случае, я полагаю, но он становится уродливым. И это не только указатели, эта же проблема относится к ссылкам. Предположительно, вы бы запретили привязывать 'i' к неконстантной ссылке (хотя это не константа lvalue) одновременно с тем, что make' & i' имеет тип 'const int *' (хотя 'i' is' int '). Это некрасиво, а это не значит, что это не сработает. –

+0

Да, кажется, что уродливо ... – mb84

4

const - концепция уровня языка. То есть, когда вы компилируете свой код и выполняете его как машинный код, все данные рассматриваются как данные более или менее. Обратите внимание, что я говорю «более или менее», потому что мы игнорируем тот факт, что в теории const данные могут быть сохранены на страницах только для чтения и при возникновении ошибок при запуске страницы; но это не является общим из-за гранулярности размеров страниц. Так что происходит следующее:

Ваш конструктор инициализирует это значение ptr, чтобы указать адрес i. Так как ваш obj объект const, вы не можете прямо изменить значение i и, кроме того, вы не можете изменить, где ptr указывает. Однако вы можете получить доступ и управлять памятью, на которую указывает ptr (в данном случае значение i).

Поэтому, поскольку компилятор не проверяет/не знает/заботится о том, что ptr указывает на i, он не обнаруживает нарушения const. Вместо этого он просто видит, что вы изменяете данные, на которые указывает ptr.

+0

Итак, компилятор не может/не может уважать/заботиться о типе 'this' внутри конструктора? 'const Test * const this; this-> ptr = & (this-> i); ' – mb84

+0

@ mb84: Да, см. ответ Стива Джессопа ниже. Нет никаких гарантий относительно 'const' до тех пор, пока объект не будет инициализирован, что после того, как один конструктор завершил выполнение объекта. – RageD

1

Steve Джессоп ответил, но для чего это стоит, вот цитата из стандарта (курсив мой):

12,1/4 Конструктор не должен быть виртуальным (10,3) или статический (9,4) ,Конструктор может быть вызван для const, volatile или const volatile объекта. Конструктор не должен быть объявлен const, volatile или const volatile (9.3.2). const и нестабильная семантика (7.1.6.1) не применяются к строительному объекту. Они вступают в силу, когда заканчивается конструктор для самого производного объекта (1.8). Конструктор не должен быть объявлен с помощью реф-квалификатора.

So *this не является постоянным объектом с точки зрения конструктора, даже когда создается постоянный объект. Это могло бы быть спроектировано по-разному, но тогда конструкторы постоянных объектов были бы гораздо менее гибкими, чем конструкторы непостоянных объектов; например, они всегда должны были инициализировать всех членов в списке инициализаторов; они не могли использовать циклы и т. д. в теле конструктора для установки значений для сложных членов.

+0

Спасибо за этот ответ, см. Также мой комментарий к Стив Джессоп. Как вы думаете? – mb84

+0

Я думаю, что было бы целесообразно иметь конструкцию-конструктор const и разрешить использовать это только для инициализации объектов const. Теперь с равномерным синтаксисом инициализации и семантикой перемещения сложные члены обычно могут быть инициализированы без тела конструктора. В любом случае объекты Const редки. Поэтому я думаю, что если бы я перепроектировал C++, я бы сделал это предложение. Но изменение его теперь, очевидно, вызовет много проблем с обратной совместимостью. – jogojapan