С++ константная функция-член

Я читаю книгу под названием «Эффективный С++, второе издание», в которой говорится о константных функциях-членах и о том, как у вас есть побитовая константность и концептуальная константность.

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

Затем есть пример функции-члена, которая, похоже, не работает побитно в тесте const.

Это выглядит так:

#include "stdafx.h"
#include <string>
#include <iostream.h>

using namespace std;

class mystring
{

public:
    mystring(const char* value);

    operator char *() const { return data; }

private:
    char * data;
};

mystring::mystring(const char * value)
{

    mystring::data = const_cast<char*>(value);
}


int main(int argc, char* argv[])
{
    const mystring s = "Hello";

    char * nasty = s;

    *nasty = 'M';

    printf("s: %c", s);

    return 0;
}

Когда это запускается, в моей книге говорится, что это должно позволить вам изменить значение s, даже если это const. Это потому, что данные char* указывают на то же, что и значение const char*. *data в данном случае не const.

Однако при попытке запустить это в MS VC++ 6.0 возникает нарушение прав доступа в строке *nasty = 'M';.

Может кто-нибудь объяснить, что происходит? Я думаю, что я что-то пропустил?

Мне кажется, что поскольку у нас есть константа mystring s, мы не должны иметь возможности ее изменить, но тогда то, что написано в книгах, кажется неудобным.


person Tony The Lion    schedule 02.07.2010    source источник
comment
Это не имеет отношения к вопросу, но имейте в виду, что и это издание книги, и Visual Studio 6 восходят ко временам, когда язык не был стандартизирован, когда многие распространенные идиомы С++ не задумывались. все же. Если вы хотите узнать, как сегодня используется C++, вам лучше использовать третье издание (которое я настоятельно рекомендую) и современный компилятор. Visual Studio Express можно загрузить бесплатно, если вы хотите придерживаться Microsoft.   -  person Mike Seymour    schedule 02.07.2010


Ответы (4)


Нарушение прав доступа происходит из-за того, что вы пытаетесь изменить строковый литерал. Ваш код эквивалентен:

char * p = "Hello";
* p = 'M';

что является незаконным как в C, так и в C++ - ничего общего с константными функциями-членами.

person Community    schedule 02.07.2010
comment
поэтому я предполагаю, что строковый литерал является неизменяемым объектом? - person Tony The Lion; 02.07.2010
comment
@Tony: не определено, является ли в любой данной реализации строковый литерал действительно неизменяемым объектом с точки зрения стандарта. Стандарт говорит вам только об этом объекте, что это неопределенное поведение для изменения объектов, которые являются постоянными (по сравнению с непостоянными объектами, доступ к которым осуществляется через постоянную ссылку - см. мой ответ) - person David Rodríguez - dribeas; 02.07.2010
comment
@ Тони Я не знаю об этом - все стандарты C и C ++ говорят, что попытка изменить один из них приводит к неопределенному поведению. - person ; 02.07.2010
comment
Обычно рекомендуется использовать семантику копирования. И в этом случае копирование строки в ваш конструктор может быть лучшим способом предотвращения такого рода проблем. С другой стороны, изменение члена данных на const и оператор приведения для возврата const char* тоже будет работать. Любой из них сделал бы вещи последовательными. Ваш конструктор не должен предполагать, что он может отбросить константу. Это еще один урок, который следует усвоить из этого примера, помимо поведения const. - person Craig Wright; 02.07.2010

Вы получаете нарушение прав доступа только потому, что указатель char* является строковым литералом. Изменение строкового литерала - это поведение undefined (AV в вашем случае), оно не имеет ничего общего с константной правильностью.

person sharptooth    schedule 02.07.2010
comment
@Tony: используй std::strings: std::string salute = "Hi"; salute = "Good bye";. Решением C было бы скопировать и изменить копию, использовать литерал для создания неконстантного объекта... char text[] = "Hi!"; text[0] = 'B'; text[1] = 'y'; text[2] = 'e'; --обратите внимание, что массивы не могут изменять размер, редактирование text на Bye работает только потому, что Hi! и Bye занимают одинаковое количество памяти. Также обратите внимание, что он определяется как char text[], а не char *text, массивы и указатели не совпадают. - person David Rodríguez - dribeas; 02.07.2010

То, что вы делаете, является неопределенным поведением. Вы отбрасываете константность и пытаетесь изменить постоянное значение. Если этот пример был скопирован как есть из книги, то книга неверна.

Что разрешено делать с const_cast<>, так это отбрасывание константы из постоянного указателя/ссылки на неконстантный объект:

int i = 5;
int const & ir = i;
const_cast<int&>(ir) = 7; // valid
int const * ip = &i;
*const_cast<int*>(ip) = 9; // valid

const int c = 11;
int const & cr = c;
const_cast<int&>(cr) = 13; // Undefined behavior

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

person David Rodríguez - dribeas    schedule 02.07.2010

В вашем примере вы не меняете s, вы пытаетесь изменить память, на которую указывает переменная-член s. Поскольку вам разрешено размещать const во многих местах, вы должны быть осторожны с тем, что вы на самом деле объявляете const.

Вашей функции-члену operator char*() const не разрешено изменять какие-либо переменные-члены. Попробуйте operator char *() const { data = "something else"; return data; }, и ваш компилятор сообщит вам, что вам не разрешено изменять data. Однако в этом случае вы просто возвращаете data без изменений. Это разрешено. Вам даже разрешено изменить память, на которую указывает data, как в operator char *() const { *data = 'M'; return data; }. Однако в вашем контексте это не работает, поскольку data указывает на строковый литерал, который вам не разрешено изменять.

person Malte Clasen    schedule 02.07.2010
comment
Так почему же это правильно: const char* v = Hello; v = Тони; ? - person Tony The Lion; 02.07.2010
comment
@Tony: v - это указатель на постоянные данные, изначально указывающий на строковый литерал, содержащий "Hello". v = "Tony" изменяет его так, чтобы он указывал на другой строковый литерал. Он не изменяет первую строку. - person Mike Seymour; 02.07.2010
comment
Будьте осторожны, где разместить этот const: char * const v = "Hello"; v = "Tony"; является постоянным указателем на неконстантные данные, поэтому присваивание приводит к ошибке компилятора. - person Malte Clasen; 02.07.2010