Pull to refresh

Comments 34

Очень напрягает, когда в библиотеках по сути const-методы таковыми не объявлены. Мешает реализовывать собственную const-логику. Например:

// Библиотека
class Container
{
public:
    unsigned count (); // не const
};

// MyModule.cpp
class MyContainer : public Container
{
public:
    void process () const 
    { 
        for (unsigned i = 0; i < count (); ++i)  // Нельзя использовать не-const count()
        {...} 
    }   
};


А если объявить MyContainer::process() как не-const, то его уж нельзя будет использовать в других const-методах. И никак этот не-const count() не завернёшь в const-метод. Конечно, можно попытаться реализовать собственный метод вроде myCount() и объявить его как const, но это, конечно, не всегда возможно.

UFO just landed and posted this here
Что же делать, если хочется последовательно, а библиотека вот не даёт?
Это обходиться прри помощи адартера обьекта.

В вашем случае:

// Вообщето православный адартер обьекта должен работать с ссылкой или указателем на адаптируемый обьект, ну да ладно
class ContainerAdapter
{
public:
unsigned count () const {return cont_.count ()}
private:
mutable Container cont_;
};

class MyContainer: public ContainerAdapter
{
//…
};
Действительно, сам не додумался :) Спасибо!
Можно проще

// Библиотека
class Container
{
public:
unsigned count (); // не const
};

// MyModule.cpp
class MyContainer: protected Container
{
public:
unsigned count() const
{
return Container::count();
}

// Ну и еще что нужно, соответственно, открываем
};
Не даст вызвать не-const Container::count() из Container::count() const.

Есть вот такой вариант:


// Библиотека
class Container
{
public:
    unsigned count (); // не const
};

// MyModule.cpp
class MyContainer : public Container
{
public:
    void process () const 
    { 
        for (unsigned i = 0; 
            i < (const_cast<MyContainer*>(this))->count (); // Приводим this к не-const типу
            ++i)  
        {...} 
    }   
};

Тогда уж вот так:
((Container*)(const_cast<MyContainer*>(this)))->count()

А вообще, убивая вот так просто слово const, Вы нарушаете гарантию, данную этим словом, что «ни один из членов класса не претерпит каких-либо изменений».
1. Зачем преобразование к Container*?
2. Согласен насчёт нарушения гарантий, но что же делать, если библиотека не даёт гарантий там, где должна бы? Конечно, не однозначный вопрос, что должна библиотека, а что нет. Однако, const-корректность библиотеки делает её более удобной. Короче говоря, лучше уж аккуратно сделать const_cast в одном изолированном месте, чем отказываться от ключевого слова const вообще.
1. Поменяйте название process на count и сразу поймете зачем. Это преобразование, в конечном счете, не сделает код более медленным или громоздким, а вот избежать ошибок и ввести лишнюю ясность поможет.
2. Я тоже предпочел бы сделать так, хоть и считаю это менее правильным. Дилемма :)
1. В таком случае нужно сделать так:
const_cast<MyContainer*>(this))->Container::count ()

поскольку преобразование типа к базовому классу не отменяет полиморфизма.
В дополнение к вышесказанному — не используйте c-style cast.
Это тоже самое, что Blackened привел в самом нечале. Нельзя вызвать не-конст ф-цию из конст.
Сделать const_cast на this. Не очень красиво, но работает.
UFO just landed and posted this here
Не поверите, но вы — первый, кто спрашивает. И — таки да, вы угадали. :)
класс. не писал на c++ со времен учебы.
но все равно интересно!
UFO just landed and posted this here
Я просто думаю, что статья на хабре хорошо читается, если её объём 3-5К; mutable и const_cast не вписались. Можно развить тему в статье «const и его друзья» :-)
Такую фотку надо вешать на книги, типа «С++ для чайников»))))
Прописная истина же.
И, я дак, например, черту не провожу, читаю как есть, так же как в случае с массивом указателей (int *a[]) или указателем на массив (int (*a)[]).

const char * a = «1»;
1) идентификатор а объявлен как
2) указатель на
3) значение const char.

char * const a = «1»;
1) идентификатор а объявлен как
2) const указатель на
3) значение char.
а еще можно делать так

class A
{
void f(int);
};

void A::f(const int);

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

Далее.
Если вы хотите объявить const поле класса, то инициализировать его обязательно списком инициализации ( так же как и ссылки )

class B
{
const int constValue;
public:
B(int&);
};

B::B()
:constValue(33)
{}

в противном случае если вы попробете сделать это в теле конструктора — получите ошибку.
В данном случае случше лучше всего руководствоваться следующим примером

аналог инициализации в теле конструктора ( между {} )
int x;
x=5;

аналог инициализации списоком инициализации ( после :)
int x = 5;

именно поэтому для инициализации констант и ссылок в качестве полей класса подходит только список инициализации
Тут нужно заметить, что члены класса лучше инициализировать в списке инициализации, в не зависимости от того, const они или нет.
...«Если вы в этом случае не реализуете не-const-методы, то во всех случаях будут молча использоваться const-методы.»…

непонятно…
Я честно пытался написать понятней, но не смог. Для пояснения написан пример.
А вообще, тут уже обсуждение интересней, чем статья :-)
> const int i(1);
> int const j(1);
>
> Все они правильные и делают одно и тоже — создают переменную,
> значение которой изменить нельзя.

Что это за переменные такие i(1) и j(1)? Просветите плиз, что это означает.
Переменные называются i и j, а int i(1); это определение переменной i типа int и инициализация её значением 1. Читайте 8.5/1.
Мда ну и синтаксис…

Зачем стандатр позволяет такие запутывающие конструкции? Ведь по синтаксису написание аналогично вызову функции.
class A
{
public:
    A(int i) {}
};

class B
{
public:
    B(int i, int j) {}
};

int main()
{
    A a1 = 1;
    A a2(2);

    int i1 = 1;
    int i2(2);

    B b1(1, 2);
}


Я доступно объяснил, или нужно прокомментировать?
Честнагря нихрена не понял. Тем более не увидел ответа на вопрос — зачем логическую конструкцию с присваиванием писать через int i2(2) вместо int i2=2?
Объясняю.

По идеологии С++ пользовательские типы не должны в использовании отличаться от встроенных. Для встроенных типов разрешена инициализация вида Type t = Initializer;, поэтому такую же инициализацию разрешили для пользовательских типов, если есть конструктор, принимающий один аргумент (см. класс A). Для пользовательских типов инициализация вида Type t(InitializerList); универсальна (см. классы A и B), поэтому для однообразности есть возможность инициализировать точно так же и экземпляры встроенных типов.
Теперь понятно. Позволяла бы карма — плюсанул.
Это инициализация этих переменных единицами. То же самое, что и const int i = 1; int const j = 1;
>Существует мнемоническое правило, позволяющее легко запомнить, к чему относится const. Надо провести черту через "*", если const слева, то оно относится к значению данных; если справа — к значению указателя.

Еще лучше мнемоническое правило было написано у Бочкова и Субботина: «изнутри наружу». Все, что участвует в создании типа объявляемой сущности: const, volatile, *, (), [] читается от имени этой сущности (самая внутренность) — наружу (то есть влево для префиксов и вправо для постфиксов). Постфиксы () [] имют приоритет перед префиксами const volatile *. Приоритет может быть изменен скобками.

В частности:
char const *pch;
      (2) (1)(0)
     <------------

pch(0) — это указатель(1) на const(2)

char* const cpch;
   (2) (1)  (0)
     <------------

pch(0) — это константный(1) указатель(2)
Sign up to leave a comment.

Articles