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

в следния код (взет от ефективния C++):

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
публикува пълния код. тестван във VC++ (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
}

получава грешка, защото []operator of 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 - правите имплицитно прехвърляне на non-const към const обект, като по този начин можете да замените:

const A temp = *this;                                // implicit cast

с

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

това също работи:

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

и тъй като можете да направите изрично предаване - вече не се нуждаете от temp, така че:

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

това ще върне non-const ref към char от този const-cast обект, който извиква претоварен operator[] :) Точно поради тази причина не можете да използвате

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

защото това е неконстантен обект; следователно ще го накара да извика функция без разходи (да претовари operator[]), което ще причини безкрайна рекурсия.

Дано има смисъл.

person EliK    schedule 23.03.2015
comment
const A a(); и A b(); са декларации на функции - person M.M; 23.03.2015
comment
те са const и non-const обект от клас 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] ще извика функцията non-const:

char &operator[](int);

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


Какво се прави в non-const функция?

Така че нека видим връзката между не-константна функция и константна функция. Във вашата програма неконстантната функция се реализира чрез извикване на константна функция.

Обикновено неконстантен обект не може да извика функцията const директно. Но можем да променим атрибута с 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 в non-const функция може да бъде заменен по следния начин:

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
@M.M Искате да кажете, че static_cast<const TextBlock&>(*this) действително връща стойност (правилна стойност ли е?), която е обвързваща към временен обект? - person WingCuengRay; 21.01.2016
comment
Не, това е препратка, която се свързва директно с *this. Няма временен обект. - person M.M; 21.01.2016
comment
@M.M Вземете го. Сгреших. Това, което казах е, че референтният тип също е обект... - 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 cast не е необходима - ще се поправи. Както и да е, моралът на историята е: 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)[позиция] вътре в неконстантната версия на оператора, тя извиква неконстантната версия, която отново извиква неконстантната версия на оператора и се превръща в безкраен цикъл. Правейки това кастинг, вие по същество извиквате const версията на същия оператор.

РЕДАКТИРАНЕ: Хм... изглежда, че проблемът не е такъв, какъвто изглежда. Имате ли копиращ конструктор за този клас? Предполагам, че това е причината за този проблем.

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

За да стане по-ясно ... static_cast<const TextBlock>(*this)[position]; Разбивката на горното твърдение би била:

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