Подрывает ли этот код систему типов C ++?

Я понимаю, что наличие метода const в C ++ означает, что объект доступен только для чтения с помощью этого метода, но в противном случае он все равно может измениться.

Однако этот код, очевидно, изменяет объект с помощью ссылки const (т.е. с помощью метода const).

Законен ли этот код на C ++?

Если да: нарушает ли это const-ность системы типов? Почему, почему нет?

Если нет: почему бы и нет?

Примечание 1. Я немного отредактировал пример, поэтому ответы могут относиться к более старым примерам.

Изменить 2: По-видимому, вам даже не нужен C ++ 11, поэтому я удалил эту зависимость.

#include <iostream>

using namespace std;

struct DoBadThings { int *p; void oops() const { ++*p; } };

struct BreakConst
{
    int n;
    DoBadThings bad;
    BreakConst() { n = 0; bad.p = &n; } 
    void oops() const { bad.oops(); }  // can't change itself... or can it?
};

int main()
{
    const BreakConst bc;
    cout << bc.n << endl;   // 0
    bc.oops();              // O:)
    cout << bc.n << endl;   // 1

    return 0;
}

Обновлять:

Я перенес лямбда-выражение в список инициализации конструктора, поскольку это позволяет мне впоследствии сказать const BreakConst bc;, что - поскольку bc сам теперь имеет значение const (а не просто указатель) - может означать (от Stroustrup), что любое изменение bc после построения должно привести к неопределенному поведению, даже если конструктор и вызывающий у них нет возможности узнать это, не видя определения друг друга.


person user541686    schedule 18.06.2012    source источник
comment
Это несколько не по теме, но void (void) является устаревшей конструкцией, поскольку void () делает то же самое, начиная с C ++ 98 и C99.   -  person moshbear    schedule 18.06.2012
comment
@moshbear: Понятия не имею, что заставило меня написать это (обратная совместимость с C или что-то в этом роде ?!); исправлено, спасибо. :-)   -  person user541686    schedule 18.06.2012
comment
Мне нравится, что ваш смайлик O:) имеет двойное значение: счастливый ангел и потрясенное испуганное лицо, в зависимости от того, с какого направления вы его читаете.   -  person Peter Olson    schedule 18.06.2012
comment
Вы можете найти больше входов здесь: stackoverflow.com/q/9939399/15161 (подобные вопросы, которые люди не как :) )   -  person slashmais    schedule 18.06.2012
comment
Вот мой вопрос, вдохновленный этой stackoverflow.com/questions/11091385/ также не требуется другая структура;)   -  person    schedule 19.06.2012
comment
@ acidzombie24: lol, хорошая работа, устраняющая необходимость в структуре, но ИМО, это в значительной степени просто обман моего вопроса. : P Это вызовет точно такую ​​же дискуссию, как здесь ...   -  person user541686    schedule 19.06.2012
comment
@Mehrdad, когда я впервые прочитал ваш вопрос (с тех пор, как я последний раз читал его, было много правок), я подумал, что вы спрашивали с точки зрения дизайна (цитирую вас. Если да: это нарушает const- о типовой системе? Почему / почему бы и нет?). Если вы чувствуете, что это слишком близко, закройте мою как обман   -  person    schedule 19.06.2012
comment
@ acidzombie24: Хм ... сложно сказать. В частности, я спрашиваю, действительно ли мой пример является дырой в системе типов (подразумевая, что я ничего не нарушал, но конечный результат все еще не определен), или есть какое-то правило, которое я случайно нарушено в какой-то момент (следовательно, это нарушение системы [или я нарушил правило]? часть). Трудно сказать, является ли ваш обман, я полагаю (именно поэтому я предложил пояснение, чтобы показать разницу), поэтому я просто оставлю это на усмотрение других, чтобы решить, если вы не знаете, что еще добавить. :)   -  person user541686    schedule 19.06.2012
comment
@ acidzombie24: И да, это тоже с точки зрения дизайна в смысле: как вызывающий должен знать, что объект не должен быть константным?   -  person user541686    schedule 19.06.2012


Ответы (5)


Метод oops () не может изменять постоянство объекта. Более того, он этого не делает. Это ваша анонимная функция. Эта анонимная функция находится не в контексте объекта, а в контексте метода main (), которому разрешено изменять объект.

Ваша анонимная функция не изменяет указатель this на oops () (который определен как const и, следовательно, не может быть изменен), а также никоим образом не наследует некоторую неконстантную переменную из этого указателя this. Сам по себе не имеет этого указателя. Он просто игнорирует указатель this и изменяет переменную bc основного контекста (которая как бы передается как параметр в ваше закрытие). Эта переменная не является константой, поэтому ее можно изменить. Вы также можете передать любую анонимную функцию, изменяющую совершенно несвязанный объект. Эта функция не знает, что она меняет объект, который ее хранит.

Если бы вы объявили это как

const BreakConst bc = ...

тогда основная функция также будет обрабатывать его как объект const и не может его изменить.

Изменить: Другими словами: атрибут const привязан к конкретному l-значению (ссылке), обращающемуся к объекту. Он не привязан к самому объекту.

person Heinzi    schedule 18.06.2012
comment
Можно ли применить те же рассуждения, если вместо создания анонимного метода внутри main я сказал BreakConst() : fn([&]() { counter++; }), counter(0) { } в конструкторе? В этом случае похоже, что метод будет в контексте объекта, но я не знаю ... - person user541686; 18.06.2012
comment
@ Mehrdad: Хороший комментарий. Я собирался спросить об этом. - person Nawaz; 18.06.2012
comment
Хайнци: Мне было бы интересно увидеть какой-нибудь текст из Стандарта, который поддерживает ваши рассуждения. - person Nawaz; 18.06.2012
comment
@Mehrdad: в этом случае у вас есть область действия конструктора, в которой вам также разрешено изменять счетчик. Если вы берете неконстантную ссылку на счетчик из конструктора и сохраняете ее в неконстантной переменной (например, в области закрытия), вам разрешается изменить ее. То же самое применимо и здесь: struct Test {Test (): _ const (0), _ nonconst (& _ const) {} int * _nonconst; int _const; }; int main () {Тестовый тест; cout ‹---------------- test._const; ++ * test._nonconst; cout ‹---------------- test._const; } - person Heinzi; 18.06.2012
comment
@Heinzi: Я думаю, проблема, связанная с этим рассуждением, заключается в том, что если я перенесу лямбду в конструктор, я мог бы впоследствии сказать const BreakConst bc;, что - потому что bc сам теперь является константой (а не просто указатель) - казалось бы, подразумевается, что изменение bc каким-либо образом после построения должно привести к UB, даже если конструктор и вызывающий не будут иметь возможности узнать это, не видя определения друг друга. Это правильно? - person user541686; 18.06.2012
comment
C ++ никогда не определяет объекты как const. Если вы скажете const BreakConst bc; тогда только ваше l-значение bc будет const. Если вы используете это обычным образом, система типов гарантирует, что вы не получите неконстантную ссылку на объект и, следовательно, вы не сможете ее изменить. Но сам объект не является константой (на нем не хранится флаг или что-то в этом роде). Вы всегда можете получить неконстантную ссылку, используя const_cast. - person Heinzi; 18.06.2012
comment
@Heinzi Я предлагаю выделить часть IOW жирным шрифтом. Я бы поставил редактирование в очередь, но добавление двух звездочек с каждой стороны не соответствует требованию шести символов. - person moshbear; 18.06.2012
comment
Примечание к моему примеру выше: функция main должна начинаться с int main () {const Test test; ...} в качестве подходящего примера. - person Heinzi; 18.06.2012
comment
@Heinzi: Ага, я только что обновил свой вопрос, чтобы он соответствовал этой проблеме. - person user541686; 18.06.2012
comment
Я только что отладил код BreakConst() : fn([&]() { counter++; }), counter(0) { } с помощью VC 2010 и понял, что счетчик внутри лямбда относится не к this->counter, а к this->__this->counter ... может ли кто-нибудь объяснить мне __ это? Он не меняет объект в основной функции. Теперь я очень запутался ... - person Alex; 18.06.2012
comment
@ Mehrdad: Я не уверен насчет этой цитаты Страуструпа. Это часть стандарта? Если да, то могу представить, почему. Это ничего не меняет в реализации константности (было бы бессмысленно хранить флаг константы в объекте), но если объект недоступен для записи, компилятору может быть разрешено выполнить некоторые дальнейшие оптимизации, которые невозможны , когда объект изменяемый. Если компилятор реализует их, запись в объект приведет к неопределенному поведению. Тогда у вас возникнут проблемы с вашим кодом ... - person Heinzi; 18.06.2012
comment
@Vash: Я предполагаю, что __ это указатель на контекст закрытия. Таким образом переменная bc основной функции передается анонимной функции. - person Heinzi; 18.06.2012
comment
@Heinzi: Ну, я имею в виду, что Страуструп - это парень, который разработал C ++, так что ... я принимаю его слово почти так же, как и стандарт. :) Так что, если вы думаете, что он совершил ошибку (или, возможно, человек, который опубликовал эту цитату ...), это, безусловно, возможность, но, к сожалению, не та, в которой я был бы уверен без дополнительной поддержки. - person user541686; 18.06.2012
comment
В цитате говорится, что это приводит к неопределенному поведению. Это предполагает, что вам разрешено изменять его компилятором (например, используя свой способ или const_cast), но не следует - возможно, из-за оптимизации компилятора, рассматривающей этот объект как неизменяемый. - person Heinzi; 18.06.2012
comment
@Heinzi, хорошо, я думал, что он получит доступ к объекту из основной функции, но это больше не правильно. В этом случае строится другой объект. Я считаю это опасным кодом: D - person Alex; 18.06.2012
comment
@Heinzi: А? Я не понимаю твоего утверждения. Когда стандарт C ++ говорит о неопределенном поведении, которое не означает, что вы можете, но вы не должны ... он говорит, что вы не должны , но у нас действительно нет возможности обеспечить соблюдение этого правила, поэтому, если вы его нарушите, мы ничего не гарантируем относительно выполнения вашей программы (или ее отсутствия). Итак, здесь не имеет значения, с помощью компилятора или с помощью какой-то черной магии - если они говорят, что изменение неизменяемого объекта - это UB, то это UB, как бы это ни происходило. (По крайней мере, так я понимаю.) - person user541686; 18.06.2012
comment
@Vash: Копирование bc основной функции в новый объект необходимо, потому что анонимная функция может быть вызвана после возврата основной функции (и ее экземпляр bc будет уничтожен). Если bc - как в этом случае - является реальным объектом, тогда ваша анонимная функция все равно приведет к неопределенному поведению, потому что тогда она обращается к недопустимому объекту. Но если bc будет просто указателем или примитивным типом, то его копирование предотвратит это. - person Heinzi; 18.06.2012
comment
@Mehrdad: Это то, что я имел в виду, говоря: «Ты можешь, но не должен». Вы можете = Компилятор позволяет вам - person Heinzi; 18.06.2012
comment
@Heinzi: Итак, я правильно понимаю: ваш окончательный ответ, что это неопределенное поведение? (Если да, можете ли вы указать точное место, в котором я вызываю UB вместо того, чтобы просто указывать на весь исходный код?) - person user541686; 18.06.2012
comment
Я не уверен, что эта ссылка является частью стандарта. Если это так, вы вызовете неопределенное поведение. Трудно указать на один фрагмент кода. Простое создание изменяющегося объекта не является неопределенным поведением. Простое создание объекта const тоже не является. Но, согласно цитате, комбинация есть. Я думаю, что на большинстве (если не на всех) текущих платформах он будет работать должным образом. Но если вы получите платформу, подобную той, что упоминается в stroustrups cite (где объекты const хранятся в аппаратной константной памяти), вы столкнетесь с проблемой. - person Heinzi; 18.06.2012
comment
@Heinzi: Да, вот в чем проблема. Проблема в том, что создатель конструктора (и вызывающий объект), похоже, не имеют возможности узнать, может ли другой нарушить константу, поскольку они могут находиться в разных файлах. Мне кажется немного странным, что это может быть undefined, но ... да ... - person user541686; 18.06.2012
comment
Не храните неконстантную ссылку на этот параметр конструктора вне конструктора. Конструктор / деструктор - единственные функции, которым разрешено изменять константные объекты. Таким образом, все другие функции, которые вы используете для их изменения, будут вести себя в соответствии с атрибутом const. - person Heinzi; 18.06.2012
comment
@Heinzi: Что, если вам нужно сохранить ссылку в другом месте? (Конструктор не знает, что объект является константой.) А что, если парень, использующий указатель, является частью кода, написанным позже (возможно, подклассом), который не знает, что указатель относится к чему-то константному? и т.д ... так много возможностей ... - person user541686; 18.06.2012
comment
Тебе никогда не придется. Всегда есть другой способ решить проблему. Например, с помощью дополнительного к конструктору метода init (). Если вы передаете неконстантный указатель подклассу, вы передаете его вне конструктора, что нарушает мое правило не использовать их вне конструктора. - person Heinzi; 18.06.2012
comment
@Heinzi Поскольку никто другой, похоже, не противоречил этому: C ++ определенно имеет константные объекты, и попытка изменить один из них является неопределенным поведением (за исключением mutable членов объекта). - person James Kanze; 18.06.2012
comment
@JamesKanze: Ага. Теперь для меня вопрос: действительно ли это нарушает const? Мне кажется, что для того, чтобы это было предназначено как UB, нам нужно указать на конкретную строку и сказать: вы можете ' не делай этого! Но из обсуждений кажется, что мой пример отлично играет по правилам на каждой строчке - просто эффект в целом оказывается чем-то неопределенным. Что меня пугает, потому что это, кажется, невозможно обнаружить, просто взглянув на объявления. Означает ли это, что я просто "сломал" константу? - person user541686; 18.06.2012
comment
@Mehrdad Вы не всегда можете указать только на одну строчку. Если бы вы могли, компилятор мог бы надежно обнаружить это, и комитет, вероятно, сделал бы это обязательной диагностикой, а не неопределенным поведением. Ваш код пытается изменить константный объект, что означает, что он имеет неопределенное поведение. - person James Kanze; 18.06.2012
comment
@JamesKanze: Хм ... для меня это немного странно. Допустим, у вас есть этот класс. Как пользователь класса должен знать, что он не должен объявлять const его экземпляр (напрямую или через другой объект, который его содержит)? - person user541686; 18.06.2012
comment
@Mehrdad Документация? Или просто не пишите такие классы. То, что вы можете подорвать систему типов, не означает, что вы должны это делать. - person James Kanze; 19.06.2012

Ваш код правильный, потому что вы не используете ссылку на константу для изменения объекта. Лямбда-функция использует совершенно другую ссылку, которая просто указывает на один и тот же объект.

В общем, такие случаи не подрывают систему типов, потому что система типов в C ++ формально не гарантирует, что вы не можете изменить объект const или ссылку на константу. Однако модификация объекта const является неопределенным поведением.

Из [7.1.6.1] CV-квалификаторы:

Указатель или ссылка на cv-квалификационный тип не обязательно должны указывать или ссылаться на cv-квалифицируемый объект, но они обрабатываются так, как если бы это было так; константный путь доступа не может использоваться для изменения объекта, даже если указанный объект не является константным объектом и может быть изменен через какой-либо другой путь доступа.

За исключением того, что любой член класса, объявленный изменяемым (7.1.1), может быть изменен, любая попытка изменить константный объект во время его жизни (3.8) приводит к неопределенному поведению.

person Rafał Rawicki    schedule 18.06.2012
comment
Константный объект? Откуда это взялось? Я думал, что просто указатель / ссылка была константой. - person user541686; 18.06.2012
comment
@Mehrdad: const Foo f; объявляет константный объект - person Alexandre C.; 18.06.2012
comment
Как сказано в моей цитате из стандарта - ссылка на константу обрабатывается так, как если бы она указывала на объект константы. - person Rafał Rawicki; 18.06.2012
comment
@ RafałRawicki: Хм, это интересно ... так ты говоришь, что это UB? Думаю, тогда мой вопрос: почему именно UB? (т.е. в какой части кода, именно я что-то нарушил?) - person user541686; 18.06.2012
comment
Я ответил, имея в виду вопрос о подрыве самой системы типов. Во-вторых, ваш образец кода не использует ссылку const, поэтому он правильный. Это хороший пример того, почему формально доказать, что функция (или, точнее сказать, вызов функции) является чистой, очень сложно. - person Rafał Rawicki; 18.06.2012
comment
-1. Путь доступа - через bc в main, привязанный к лямбда-выражению и, следовательно, не являющийся константой. - person MSalters; 18.06.2012
comment
@MSalters Я отредактировал свой ответ и написал это под стандартной цитатой, прежде чем вы дали мне -1. - person Rafał Rawicki; 18.06.2012
comment
@ RafałRawicki: Теперь неясно, как вы пришли к выводу, что это неопределенное поведение. - person MSalters; 18.06.2012
comment
@MSalters Я думаю, что сейчас я сделал свой ответ более ясным, я был больше сосредоточен на правильности системы типов, чем на этом конкретном примере. Спасибо за руководство. - person Rafał Rawicki; 18.06.2012

Нечто подобное я уже видел. По сути, вы вызываете функцию стоимости, которая вызывает что-то еще, что изменяет объект, не зная об этом.

Учтите также это:

#include <iostream>
using namespace std;

class B;

class A
{
    friend class B;
    B* pb;
    int val;
public:
    A(B& b); 
    void callinc() const;
    friend ostream& operator<<(ostream& s, const A& a)
    { return s << "A value is " << a.val; }
};

class B
{
    friend class A;
    A* pa;
public:
    void incval() const { ++pa->val; }
};

inline A::A(B& b) :pb(&b), val() { pb->pa = this; }
inline void A::callinc() const { pb->incval(); }


int main()
{
    B b;
    const A a(b);  // EDIT: WAS `A a(b)`
    cout << a << endl;
    a.callinc();
    cout << a << endl;
}

Это не C ++ 11, но делает то же самое: дело в том, что const не является транзитивным.

callinc() не меняет себя a и incval не меняет b. Обратите внимание, что в main вы даже можете объявить const A a(b); вместо A a(b);, и все будет компилироваться одинаково.

Это работает десятилетиями, и в вашем примере вы просто делаете то же самое: просто вы заменили класс B на лямбда.

ИЗМЕНИТЬ

Изменен main (), чтобы отразить комментарий.

person Emilio Garavaglia    schedule 18.06.2012
comment
В вашем примере не рассматривается проблема const object (после моих обновлений вопроса на основе другого ответа). Это то, что меня пугает. - person user541686; 18.06.2012
comment
@ Mehrdad: Смотрите мою редакцию. Вы это имели в виду? Оно работает! (Или ... Нет, потому что, по вашему замыслу, не должно) - person Emilio Garavaglia; 19.06.2012

Проблема заключается в сравнении логической константы и побитовой константы. Компилятор ничего не знает о логическом значении вашей программы и применяет только побитовые константы. Вам решать, как реализовать логическую константу. Это означает, что в тех случаях, как вы показываете, если указанная память является логической частью объекта, вам следует воздерживаться от изменения ее в константной функции, даже если компилятор позволит вам (поскольку она не является частью побитового изображения объекта). Это также может означать, что если часть побитового изображения объекта не является частью логического значения объекта (например, встроенного счетчика ссылок или кэшированных значений), вы делаете это mutable или даже отбрасываете const, в случаях где вы изменяете его, не изменяя логическое значение объекта.

person James Kanze    schedule 18.06.2012

Функция const просто помогает от случайного неправильного использования. Он не предназначен для предотвращения взлома специального программного обеспечения. Это то же самое, что и частное и защищенное членство, кто-то всегда может взять адрес объекта и увеличить память для доступа к внутренним компонентам класса, нет способа остановить это.

Итак, да, вы можете обойти const. По крайней мере, вы можете просто изменить объект на уровне памяти, но это не означает, что const не работает.

person Stefan    schedule 18.06.2012
comment
Функция const просто помогает против случайного неправильного использования - я думаю, вопрос в том, почему это не квалифицируется как случайное неправильное использование? - person user541686; 18.06.2012
comment
Это больше в моем следующем предложении. Оно не предназначено для предотвращения взлома специализированного программного обеспечения ;-) Невозможно предотвратить атаки на данные на уровне памяти. - person Stefan; 18.06.2012
comment
Что вы имеете в виду под уровнем памяти? Какие еще есть уровни? - person user541686; 18.06.2012
comment
Например, если вы объявляете класс и называете закрытый член, я не могу изменить его с помощью кода из другого класса (если вы не предпримете шаги, чтобы разрешить это, например, сделайте меня другом). Это безопасно на уровне кода. Однако я могу взять адрес вашего объекта, получить доступ к вашему члену на уровне памяти и изменить его. Это невозможно остановить. Это не означает, что «частный» взломан, просто это было предназначено для предотвращения случайного неправильного использования, а не целенаправленной атаки. - person Stefan; 18.06.2012
comment
Хм ... это отличный момент, но это немного другой момент. доступ к чему-то private не вызывает неопределенное поведение, насколько я знаю, но const вызывает! Поэтому мне кажется разумным, что если что-то вызывает UB, вы должны иметь возможность каким-то образом от него защититься, но я не понимаю, как вы можете это сделать здесь. - person user541686; 18.06.2012
comment
Я думал, что принципы такие же? Программист должен не пытаться обойти эти конструкции, вы можете легко получить доступ к унифицированному указателю и получить UB. Компилятор и стандарты предлагают защиту только на базовом уровне (константы, уровни доступа и т. Д.). Если пользователи обходят их стороной, то они сами по себе. - person Stefan; 18.06.2012
comment
Принципы те же, но последствия - нет. В случае с private это просто означает, что вы обошли защиту .... ладно, неважно. В случае const это означает, что нет способа определить, приведет ли создание объекта const определенного класса к определенному поведению, которое ставит под угрозу ваш собственный код! Для меня это довольно радикальная разница. - person user541686; 18.06.2012
comment
Принципы те же, но последствия - нет. В случае с частным Ах, в этом случае мы пришли к насильственному соглашению! Я только ссылался на принципы (простой защиты от причинного злоупотребления, а не от атаки кода). Я не обращал внимания на различную потенциальную серьезность ситуаций, я согласен с тем, что с точки зрения серьезности или причинения потенциальной реальной аварии они разные. - person Stefan; 18.06.2012
comment
Думаю, я всегда думал, что если вы играете по правилам (что, по общему признанию, сложно), C ++ может, по крайней мере, дать вам определенные гарантии. Теперь вы говорите мне, что это неправда - ваш код может взорваться даже, если вы будете играть полностью по правилам? Для меня это новая концепция - для меня это означало бы, что это нарушает систему типов, поскольку даже когда я играю по правилам, это все равно разрушает мою программа. - person user541686; 18.06.2012
comment
Вы уверены, что это новая концепция? Если вы объявляете указатель и получаете к нему доступ без его инициализации, значит, вы не нарушили конкретное правило компилятора / языка, но вам повезет, что он не взорвется. - person Stefan; 18.06.2012
comment
Повезло не взорваться! = Не повезло взорваться. Я никогда не говорил о том, что неопределенное поведение должно давать (или не давать) разумные результаты. Очевидно, это не определено. :-) Я спрашивал о разумном поведении, дающем неопределенные результаты. - person user541686; 18.06.2012
comment
Тогда мы, кажется, говорим об объективном v субъективном (т. Е. «Разумное поведение» субъективно). Объективный факт состоит в том, что эти языковые конструкции (например, система типов) не обеспечивают абсолютной защиты, особенно если кто-то пытается их обойти. Субъективно, некоторые могут быть легче обойти, чем другие, случайно или с помощью злонамеренных методов кодирования, но это не имеет отношения к моей точке зрения. - person Stefan; 18.06.2012
comment
Возможно - но я еще не совсем уверен, что const должен быть настолько субъективным. Мы слышали о том, что код является корректным с константой, что (предположительно) объективно можно проверить. Я никогда не слышал о том, чтобы код был частным (или что-то еще). Это само по себе говорит мне, что должен быть способ проверки правильности кода, который использует const, только на основе объявлений (а не реализаций вызываемого кода). Тот факт, что люди никогда не говорят о частном исправлении и т. Д., Говорит мне, что аналогия с другими средствами защиты и неопределенным поведением может быть не на 100% точной ... но, возможно, я ошибаюсь. - person user541686; 18.06.2012
comment
Извините, я не имел в виду субъективность const. Я имел в виду вашу точку зрения о разумном поведении, которое является субъективным. Константа - это единое понятие, следовательно, у вас есть "правильная константа". Частный доступ был бы компонентом концепции инкапсуляции и сокрытия данных, вероятно, поэтому фраза «частный правильный» не используется. - person Stefan; 19.06.2012