разница между static_cast‹const A›(*this) и static_cast‹const A&›(*this)

в следующем коде (взятом из эффективного С++):

class A 
{
  ....
  char& operator[](std::size_t position)         // now just calls const op[]
  {
    return
      const_cast<char&>(           // cast away const on op[]'s return type;
        static_cast<const TextBlock&>(*this)   // add const to *this's type;
          [position]                           // call const version of op[]
      );
  }

  const char& operator[](int index) const
  {
     ...
  }
}
//complete example, tested with VC 2010
#include<iostream>
#include<string>

class TextBlock
{
public:
    TextBlock(std::string st):text(st){};
    TextBlock(char* cstr): text(cstr){};
    TextBlock(const TextBlock& r)
    {
        std::cout<<"copy constructor called"<<std::endl;
    }
    char& operator[](int index)
    {
        std::cout<<"non-const operator"<<std::endl;
        return const_cast<char&>(static_cast<const TextBlock>(*this)[index]);
    }

    const char& operator[](int index) const
    {
        std::cout<<"const operator"<<std::endl;
        return text[index];
    }

private:
    std::string text;
};

int main()
{
    TextBlock rt("hello");
    std::cout<<rt[0]<<std::endl;
}

В этом коде, если вы измените static_cast с const TextBlock& на const TextBlock, это приведет к рекурсивному вызову неконстантной версии operator[]. Может ли кто-нибудь объяснить, в чем причина этого (почему const TextBlock приводит к тому, что не вызывается const функция-член operator[]).


person user299582    schedule 27.09.2010    source источник
comment
Какой компилятор вы используете? Я попробовал, и в обоих случаях он вызывает const-версию. Вот пример, который я использовал: codepad.org/uZ1q4yNu   -  person reko_t    schedule 27.09.2010
comment
Проверьте ответ... У меня есть причина :).   -  person Vite Falcon    schedule 27.09.2010
comment
Можете ли вы также вставить полный код, который вы используете, может быть что-то еще, что вызывает поведение.   -  person reko_t    schedule 27.09.2010
comment
Не могу смоделировать поведение, которое вы цитируете   -  person Chubsdad    schedule 27.09.2010
comment
То же самое. (Не удалось воспроизвести поведение)   -  person Vite Falcon    schedule 27.09.2010
comment
выложил полный код. проверено в VС++ (2010)   -  person user299582    schedule 27.09.2010


Ответы (5)


Причина в том, что

const A a();

а также

A b();

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

cout << a[0] << endl;

является законным, но

cout << b[0] << endl;

не является. По этой причине вы должны перегрузить оператор [] для неконстантного объекта. Чтобы избежать копирования кода, автор предлагает использовать функцию для константного объекта, отбрасывая его константность. По этой причине вы получаете:

char& operator[](std::size_t position)
{
     return const_cast <char &>( static_cast <const A &>(*this) [position] );
}

другими словами, вы просто конвертируете свой объект в const

char& operator[](std::size_t position)
{
     const A temp = *this;      //create a const object 
                                    //and assign current object to it
     ....
}

попробуйте использовать []оператор const obj

char& operator[](std::size_t position)
{
     const A temp = *this;      //create a const object 
                                    //and assign current object to it
     return temp[position];     // call an overloaded operator [] 
                                // of the const function
}

получить ошибку, потому что []оператор функции const возвращает const char&, а эта функция возвращает char&. Таким образом, отбросьте постоянство

char& operator[](std::size_t position)
{
     const A temp = *this;      //create a const object 
                                //and assign current object to it
     return const_cast <char &>( temp[position] );
 }

Теперь все готово. Вопрос: «Как

const A temp = *this;
return const_cast <char &> (temp[position]);

стало так:

return const_cast <char &> ( static_cast <const A &> (*this)[position]);

? Причина этого в том, что когда вы используете temp - вы делаете неявное приведение неконстантного к константному объекту, поэтому вы можете заменить:

const A temp = *this;                                // implicit cast

с

const A temp = static_cast <const A &> (*this)        //explicit  

это также работает:

const A temp = const_cast <const A &> (*this) 

и поскольку вы можете сделать явное приведение - вам больше не нужен временный, таким образом:

return const_cast <char &> (static_cast <const A &>(*this)[position]);

это вернет неконстантную ссылку на char из этого константного объекта, который вызывает перегруженный оператор [] :) Именно по этой причине вы не можете использовать

return const_cast <char &> ((*this)[position]);

потому что это неконстантный объект; следовательно, это заставит его вызвать нестоимостную функцию (перегрузить оператор []), что вызовет бесконечную рекурсию.

Надеюсь, это имеет смысл.

person EliK    schedule 23.03.2015
comment
const A a(); и A b(); являются объявлениями функций. - person M.M; 23.03.2015
comment
они являются константными и неконстантными объектами класса A с конструкторами по умолчанию, по крайней мере, в соответствии с проблемой;) - person EliK; 23.03.2015

Другая функция operator[], которую мы вызывали

Когда вы определяете два объекта, один из них является константным, а другой неконстантным, например: const TextBlock a("Hello"); TextBlock b("World");

Когда вы используете a[0] и b[0], будут вызываться разные типы operator[]. Первый из них -- a[0] будет вызывать функцию const:

const char &operator[](int) const;

Поскольку a является константным объектом, каждая операция над ним не может изменить его значение. Поэтому, когда он использует оператор [], будет вызвана версия const и вернет тип const char&, что также означает, что мы не можем изменить значения ее членов.

С другой стороны, второй -- b[0] вызовет неконстантную функцию:

char &operator[](int);

Объект можно изменить, также как и его член. Итак, эта функция возвращает тип char &, который можно изменить.


Что делается в неконстантной функции?

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

Обычно неконстантный объект не может напрямую вызывать константную функцию. Но мы можем изменить атрибут на static_cast<>. Таким образом, мы можем преобразовать объект b в const TextBlock, чтобы он мог вызывать функцию const.

Мы делаем это в char &operator[](int index), что может уменьшить повторное использование кода.

char & operator[](int position) { // const TextBlock tmp = *this; // return const_cast<char &>(tmp[position]); return const_cast<char &>( (static_cast<const TextBlock &>(*this))[position] ); }

При использовании static_cast<const TextBlock &>(*this) он неявно определяет временный объект, который является преобразованием TextBlock & в const TextBlock & в *this.

После преобразования у нас есть временный объект, и мы назвали его tmp. Таким образом, мы можем использовать tmp[0] для вызова функции const, что мы и делаем.

После возврата из функции const мы получаем значение типа const char &. Но на самом деле мы хотим, чтобы это было char &. Поэтому мы используем const_cast<char &> для удаления атрибута const из возвращаемого значения. После этого мы получаем неконстантную ссылку в char, значения которой мы можем модифицировать.

Собственно, оператор return в неконстантной функции можно заменить так:

const TextBlcok &tmp = *this; return const_cast<char &>tmp[position];

tmp — это временная ссылка на *this (не временный объект). У нас есть неявное преобразование из TextBlock & в const TextBlock & здесь, тогда как в исходном операторе возврата есть явное преобразование.

Не забудьте добавить & в первое выражение, которое означает, что мы используем ссылку на объект вместо реального объекта. Или он вызовет конструктор присваивания для создания нового объекта. Если это произойдет, tmp не будет иметь ничего общего с *this. Это разные объекты, даже если они имеют одинаковое значение. Что бы мы ни изменили в tmp, фактический объект, которым мы хотим управлять, не изменится!

person WingCuengRay    schedule 20.01.2016
comment
он неявно определяет временный объект, который является преобразованием TextBlock & в const TextBlock & для *this. - на самом деле это прямая привязка. - person M.M; 20.01.2016
comment
@MM Вы имеете в виду, что static_cast<const TextBlock&>(*this) на самом деле возвращает значение (это правильное значение?), Которое привязывается к временному объекту? - person WingCuengRay; 21.01.2016
comment
Нет, это ссылка, которая напрямую связана с *this. Нет временного объекта. - person M.M; 21.01.2016
comment
@ М. М. Пойми. Я был неправ. Я сказал, что ссылочный тип также является объектом... - person WingCuengRay; 21.01.2016
comment
ХОРОШО. Ссылки не являются объектами. Нет и временных ссылок. Может быть, отредактируйте свой пост, чтобы прояснить это предложение - person M.M; 21.01.2016

Приведенный ниже код работает — с возвращаемыми значениями, измененными на char, чтобы избежать проблемы, обнаруженной reko_t при возврате ссылки на уже исчезнувшее временное — на g++ 3.4.6.

Чтобы объяснить проблему...

static_cast<const TextBlock>(*this)

... фактически то же самое, что и...

const TextBlock temporary = *this;

... который вы затем индексируете и возвращаете ссылку. Но это временное исчезает из стека к тому времени, когда используется ссылка. Учитывая, что вы возвращали такую ​​ссылку, ваше поведение было технически неопределенным.

Ваш компилятор работает с приведенным ниже кодом? (тип позиции стандартизирован на int во избежание двусмысленности).

#include <iostream>

struct A  
{ 

  char operator[](int position)         // now just calls const op[] 
  { 
    return 
        static_cast<const A>(*this)     // add const to *this's type; 
          [position];                   // call const version of op[] 
  } 

  const char operator[](int index) const 
  { 
    return x_[index];
  } 

  char x_[10];
};

int main()
{
  A a;
  strcpy(a.x_, "hello!");
  const A& ca = a;
  std::cout << a[0] << ca[1] << a[2] << ca[3] << a[4] << ca[5] << '\n';
}
person Tony Delroy    schedule 27.09.2010
comment
Пробовал это в Visual C++ (2010), не компилируется. ошибка C2440: 'const_cast': невозможно преобразовать из 'const char' в 'char - person user299582; 27.09.2010
comment
Да, извините... теперь вы не возвращаете ссылку, приведение const не нужно - исправим. Во всяком случае, мораль этой истории такова: static_cast‹const A&›, а не ‹const A›, и у вас не будет временного. - person Tony Delroy; 27.09.2010
comment
Пробовал в g++, код работает. Без проблемы, которую вы сказали как неопределенную. Это не имеет смысла. Я думаю, вы правы. Я тестирую с a[0] = 'D'; cout << a[0], согласно вашему утверждению, a[0] будет ссылаться на временный объект, а исходный объект никогда не модифицируется. Но результат показывает, что получилось! - person Joey.Z; 13.08.2013
comment
Я бы описал возврат висячей ссылки (которая затем используется) как очень неопределенную, а не технически :) - person M.M; 20.01.2016

char& operator[](std::size_t position) и char& operator[](std::size_t position) const разные. Обратите внимание на «const» после объявления функции. Первая — это «неконстантная» версия оператора, а вторая — это константная версия оператора. Неконстантная операторная функция вызывается, когда экземпляр этого класса не является константным, а константная версия вызывается, когда объект является константным.

const A a;
char c = a[i]; // Calls the const version
A b;
char d = b[i]; // Calls the non-const version

Когда вы говорите (*this)[position] внутри неконстантной версии оператора, она вызывает неконстантную версию, которая снова вызывает неконстантную версию оператора, и получается бесконечный цикл. Выполняя это приведение, вы, по сути, вызываете константную версию того же оператора.

РЕДАКТИРОВАТЬ: Хм... похоже, проблема не в том, чем кажется. У вас есть копирующий конструктор для этого класса? Я предполагаю, что это то, что вызывает эту проблему.

EDIT2: я думаю, что понял. a[i] -> создает временную переменную при приведении const без ссылки (const A) -> temp[position] -> создает временную переменную при приведении const без ссылки -> temp2[position] -> и продолжается.....
в то время как переменная с const приведена с ссылкой (const A&), временная переменная не создается, что позволяет избежать мертвого цикла.

Чтобы было понятнее ... static_cast<const TextBlock>(*this)[position]; Разбивка приведенного выше утверждения будет следующей:

  • Преобразовать *this в const TextBlock
  • Создайте временную переменную для хранения const TextBlock (конструктор копирования, вызываемый передачей const TextBlock)
  • Вызовите оператор [] для временной переменной, которая НЕ является константой, потому что она была временной.
  • Временная переменная проходит описанный выше процесс.
person Vite Falcon    schedule 27.09.2010
comment
Я сделал ошибку в первоначальном сообщении, я забыл пометить второй оператор [] как константную функцию-член. - person user299582; 27.09.2010
comment
Я понимаю, что вызов (*this)[position] вызывает неконстантную версию, но мой вопрос в том, почему static_cast‹const A›(*this)[i] вызывает неконстантную версию, а static_cast‹const A&›( *this)[i] вызывает константную версию. Оба сначала приводят *this к константному объекту. - person user299582; 27.09.2010
comment
Все дело в том, зачем ему вызывать неконстантную версию operator[]. Если он вызовет const-версию, он создаст временную только один раз; он не должен делать это рекурсивно (проверьте пример кодовой панели в моем другом комментарии, он демонстрирует, что это НЕ то, что происходит). Он вызывает неконстантную версию оператора [] только один раз. - person reko_t; 27.09.2010
comment
@reko_t: Просто разбил все заявление для ясности. - person Vite Falcon; 27.09.2010
comment
При этом в любом случае нет абсолютно никаких причин делать приведение без ссылки, поскольку, как вы сказали, это приведет к временной копии, которая просто бессмысленна. И это также может вызвать очень странное поведение, поскольку operator[] возвращает ссылку на char, после временной копии эта ссылка может даже не указывать на исходную память объекта. - person reko_t; 27.09.2010
comment
Вызовите оператор [] для временной переменной, которая НЕ является константой, потому что она была временной. Почему временная переменная не может быть константой? Это явно константа, иначе этого бы не произошло: codepad.org/uZ1q4yNu - person reko_t; 27.09.2010
comment
Вот более наглядный пример: codepad.org/cMMW8SFe Как видите, он действительно вызывает константную версию оператора [] после создания временного файла. Я предполагаю, что это может работать по-разному в разных компиляторах, хотя я не думаю, что что-то подобное должно быть неопределенным поведением. - person reko_t; 27.09.2010

У ваших операторов разные типы параметров

char& operator[](std::size_t position)
const char& operator[](int index) const ‹- Также должно быть std::size_t

Это может быть решение, которое вы ищете. Были ли в примере из книги разные типы для inparameter? Помните, что приведение типов работает с возвращаемым значением.

char& operator[](std::size_t index)
{
  std::cout<<"non-const operator"<<std::endl;
  const TextBlock &  ref = *this;
  return const_cast<char&>(ref[index]);
}
person Per-Åke Nilsson    schedule 27.09.2010