Tuli tässä mieleen seuraavanlainen tapaus:
#include <iostream> class A { private: int _data[5]; public: A() { _data = {0}; } const int& operator [](int i) const { std::cout << "get" << std::endl; return _data[i]; } int& operator [](int* i) { std::cout << "set" << std::endl; return _data[*i]; } }; int main() { A a = A(); int i = 4; a[&i] = 5; // Asettaa 4. alkion 5:ksi, eikä kutsu hakijaa. std::cout << a[i] << std::endl; // Tulostaa 5, eikä kutsu asettajaa. return 0; }
Tässä pitäisi pystyä määrittelemään hakemiselle ja asettamiselle eri funktiokutsut. Asettamisessa tarvitsee tehdä sellaisia asioita, mitä ei hakemisessa pidä tehdä. Sain homman pelaamaan ylläolevalla tavalla, mutta onko mitään kätevämpää tapaa?
Ethän tuossakaan saa varsinaisesti asettamista kiinni, vaan joudut luottamaan siihen, että koodissa käytetään haluamaasi merkintätapaa. Lisäksi viritelmäsi on hyvin virhealtis.
Onko jokin erityinen syy välttää tavallisia funktiokutsuja?
a.aseta(i, 5); // Selkeä funktiokutsu erotukseksi hausta.
Funktiokutsun voi piilottaa käyttämällä ylimääräistä luokkaa:
class A { int _data[5]; protected: void aseta(int i, int x) { _data[i] = x; } int hae(int i) { return _data[i]; } class B { A& a; int i; public: B(A& _a, int _i): a(_a), i(_i) {} const B& operator = (int x) const { a.aseta(i, x); return *this; } operator int () const { return a.hae(i); } }; public: const B operator [] (int i) { return B(*this, i); } };
Kääntäjä osaa optimoida tuosta kaiken ylimääräisen pois (GCC:llä jo -O riittää), joten ero on täysin esteettinen.
Metabolix kirjoitti:
Ethän tuossakaan saa varsinaisesti asettamista kiinni, vaan joudut luottamaan siihen, että koodissa käytetään haluamaasi merkintätapaa. Lisäksi viritelmäsi on hyvin virhealtis.
Totta. Tarkoitus on saada homma niin sanotusti näkymättömäksi.
Kiitos vinkistä, esittelemäsi tapa on todellakin paljon parempi.
Käsitellään nyt saman tien asian toinenkin puoli: Operaattorista on mahdollista kirjoittaa sekä const- että ei-const-versio. Tällöin kutsutaan ensisijaisesti ei-const-versiota, vaikkei erityistä tarvetta olisi, joten esitetty ongelma ei sillä ratkea. Kuitenkin const-versio operaattorista voi olla tarpeen (myös yllä), jos ohjelma käsittelee const A -tyyppistä oliota. (Const-sanaa on syytä käyttää esimerkiksi silloin, kun funktio ottaa A-olion parametrinaan eikä aio muokata sitä. Voi toki jättää käyttämättäkin, jos ei välitä hyvistä ohjelmointitavoista ja koko projekti on tehty samalla asenteella.)
Operaattorin const-version käyttö on mahdollista pakottaa yksinkertaisella tyypinmuunnoksella:
std::cout << ((const A&)a)[2] << std::endl;
Metabolix kirjoitti:
Operaattorin const-version käyttö on mahdollista pakottaa yksinkertaisella tyypinmuunnoksella:
Noinhan sen olisi tosiaan saanut toimimaan, eipä tullut illalla mieleen. :) Toteutin tuon fiksumman version projektiin, niin nyt onnistuu mukavan näkymättömästi asiat.
Yksi idea vielä: voihan tavallisenkin sulkuoperaattorin määritellä samalla tavalla, joten voisi käyttää toiseen operaatioista merkintää a(x). Ei tosin tämäkään pärjää luokkaviritelmälle huomaamattomuudessa, mutta näinkin ratkeavat kaikki muut alkuperäisen viritelmän ongelmat (kuten välitulosten käyttö indekseinä), eli päästään samaan tulokseen kuin const-tyypinmuunnoksen kanssa.
Aihe on jo aika vanha, joten et voi enää vastata siihen.