Mutta C++:ssa jos minulla on luokassa A vastaava ei-staattinen metodi
"metodi", niin miksi osoitin siihen pitää määritellä
void(A::*p)();
ja asettaa "p = &A::metodi"? Miksi tuo &-merkki tarvitaan kun aiemmassa sitä
ei tarvita? Mihin luokan nimeä tarvitaan tuossa määrittelyssä? Eikö "metodi"
ole kuitenkin "vain" funktio tietyllä nimellä, paluuarvolla ja
parametreilla?
Mitä missaan?
> Mutta C++:ssa jos minulla on luokassa A vastaava ei-staattinen metodi
> "metodi", niin miksi osoitin siihen pitää määritellä
> void(A::*p)();
> ja asettaa "p = &A::metodi"? Miksi tuo &-merkki tarvitaan kun aiemmassa sitä
> ei tarvita?
Koska nuo ovat eri otuksia ja koska se on C:ssä "väärin" ja C++:ssa enemmän
"oikein" tai johdonmukaisemmin. C++:ssa myös vapaan funktion tapauksessa voi
käyttää &-operaattoria.
> Mihin luokan nimeä tarvitaan tuossa määrittelyssä? Eikö "metodi"
> ole kuitenkin "vain" funktio tietyllä nimellä, paluuarvolla ja
> parametreilla?
Koska funktio on luokan jäsen, siihen ei voi viitata ilman tuota luokkaa. Ja
mitenkäs sattuisi jos meillä olisit luokat A ja B joissa on molemmissa
samanniminen metodi?
Ei-staattista jäsenfunktiota ei voi myöskään kutsua ilman luokan ilmentymää,
siis oliota:
struct A
{
void f();
};
void (A::*p)() = &A::f;
A a;
A* b = &a;
(a.*p)();
(b->*p)();
--
Jouko
Mitä "väärin" ja "enemmän oikein" tässä tarkoittaa? Onko kyse vain valitusta
syntaksista vai löytyykö tästä jotain hienompaa filosofiaa taustalta?
>> Mihin luokan nimeä tarvitaan tuossa määrittelyssä? Eikö
>> "metodi" ole kuitenkin "vain" funktio tietyllä nimellä, paluuarvolla ja
>> parametreilla?
>
> Koska funktio on luokan jäsen, siihen ei voi viitata ilman tuota luokkaa.
> Ja mitenkäs sattuisi jos meillä olisit luokat A ja B joissa on molemmissa
> samanniminen metodi?
Miksi sillä on merkitystä? Mitä metodissa on sellaista luokkasidonnaista
ettei pelkkä seen tyylinen malli toimi? Siis muuta kuin se, että instanssi
vaaditaan kutsussa? Kyseessä on kuitenkin "vain" funktio, joka palauttaa
jotain ja ottaa parametreikseen jotain... vai?
Siis jos on void A::metodi(), niin sehän on funktio paluuarvolla ja
parametreilla? Eikö se kuitenkin käänny funktioksi? Vai tuleeko funktion
nimeen käännöksessä jotain luokkaan viittaavaa? (se tuntuisi
luonnollisimmalta selitykseltä, jotenkinhan ne tosiaan pitää pystyä
erottelemaan... hmm..)
>> Koska funktio on luokan jäsen, siihen ei voi viitata ilman tuota luokkaa.
>> Ja mitenkäs sattuisi jos meillä olisit luokat A ja B joissa on molemmissa
>> samanniminen metodi?
>
> Miksi sillä on merkitystä? Mitä metodissa on sellaista luokkasidonnaista
> ettei pelkkä seen tyylinen malli toimi?
Metodi itsessään on luokkasidonnainen. Luokka voi olla (ja usein on)
enemmän kuin pelkkä paketti, jonka sisään samaan asiaan liittyviä
funktioita kasataan. Jos haluat vain ryhmittää kasan funktioita
yhteen, namespace -avainsana on siihen kelvollinen tapa.
> Siis muuta kuin se, että instanssi
> vaaditaan kutsussa? Kyseessä on kuitenkin "vain" funktio, joka palauttaa
> jotain ja ottaa parametreikseen jotain... vai?
Kyllä ja ei. Instanssi vaaditaan kutsussa, koska metodin toiminta
voi riippua siitä mille instanssille sitä kutsutaan. Ts. parametrien
lisäksi myös instanssin tila voi vaikuttaa asioihin. Luokkametodin
kohdalla instanssilla ei olisi merkitystä, mutta luokalla voi silti
olla jotain sisäisiä vakioita tai muuta vaikuttavaa tavaraa.
> Siis jos on void A::metodi(), niin sehän on funktio paluuarvolla ja
> parametreilla? Eikö se kuitenkin käänny funktioksi?
A::metodi on enemmän kuin funktio, vaikka tietenkin voidaan kirjoittaa
yksinkertainen esimerkki, jossa ero ei ilmene.
> Vai tuleeko funktion
> nimeen käännöksessä jotain luokkaan viittaavaa? (se tuntuisi
> luonnollisimmalta selitykseltä, jotenkinhan ne tosiaan pitää pystyä
> erottelemaan... hmm..)
Käännöksessä se voi kääntyä eri tavoilla tapauksesta riippuen.
Esimerkiksi dynaaminen sidonta (virtual avainsana metodin edessä) vaatii
lisäinformaatiota myös käännettyyn binääriin, kun metodia kutsutaan
kantaluokkaosoittimen läpi ohjelmakoodissa.
Käännösvaiheessa näkyvyysaluemääreet ja friend-avainsana vaikuttavat
asioihin siten että A::metodi voi tehdä asioita joita metodi
A:n ulkopuolella ei voi.
--
// Antti Virtanen -//- http://www.students.tut.fi/~virtanea -//- 050-4004278
"Elisen väitöskirja sisältää metamorfoottisen Pallo - Flyygeli - Jänis
- "hassukaavan" ja omatekoisen, humanistisen "opin" flygelismin teemat,
joilla kuvataan taiteentekemisen prinsiippejä ja perustunnelmia, myös
eettisiä ja sielullisia."
Riitta Nelimarkka-Seeck, tohtorin väitöskirjan abstrakti
"Antti Virtanen" <virt...@maakotka.cs.tut.fi> kirjoitti
> On 2006-09-04, an Onyymi <r...@s.t.u.v> wrote:
>
>> Miksi sillä on merkitystä? Mitä metodissa on sellaista luokkasidonnaista
>> ettei pelkkä seen tyylinen malli toimi?
>
> Metodi itsessään on luokkasidonnainen. Luokka voi olla (ja usein on)
> enemmän kuin pelkkä paketti, jonka sisään samaan asiaan liittyviä
> funktioita kasataan. Jos haluat vain ryhmittää kasan funktioita
> yhteen, namespace -avainsana on siihen kelvollinen tapa.
Olen ymmärtänyt funktion ja metodin olevan siinä mielessä yhtälaisia, että
molempien suoritus alkaa jostain kohdasta muistia. Olen myös ymmärtänyt,
että osoitin funktioon/metodiin on osoitin tuohon kohtaan muistia. Jotta
kutsussa olisi mieltä, niin osoittimen pitää olla "yhteensopiva"
määrittelyltään, että parametrit ja paluuarvo saadaan välitettyä.
Jos asia olisi kuten olen ymmärtänyt, niin määrittelyllä ei pitäisi
käsittääkseni olla eroa. Olen siis ymmärtänyt jotain väärin.
Aiemmin epäilin metodin nimeen tulevan luokan nimen mukaan ja ajattelin
josko se selittäisi jotain, mutta se ei kyllä selitä. Jos metodille
käännösvaiheessa menisi vaikka sen luokan osoitin (eli *this), niin
parametrilistan muuttuminen selittäisi mistä on kysymys, mutta kun ei
mene...:(
No, ei tuolla sinänsä ole merkitystä. Olen vain see-partana koittanut
opetella c++:aa ja tätä kohtaa en ymmärtänyt.
> Tässä taitaa nyt käydä niin, että joko en ei-akateemisena osaa ilmaista
> itseäni tai lukea vastausta.
>
> "Antti Virtanen" <virt...@maakotka.cs.tut.fi> kirjoitti
>> On 2006-09-04, an Onyymi <r...@s.t.u.v> wrote:
>>
>>> Miksi sillä on merkitystä? Mitä metodissa on sellaista luokkasidonnaista
>>> ettei pelkkä seen tyylinen malli toimi?
>>
>> Metodi itsessään on luokkasidonnainen. Luokka voi olla (ja usein on)
>> enemmän kuin pelkkä paketti, jonka sisään samaan asiaan liittyviä
>> funktioita kasataan. Jos haluat vain ryhmittää kasan funktioita
>> yhteen, namespace -avainsana on siihen kelvollinen tapa.
>
> Olen ymmärtänyt funktion ja metodin olevan siinä mielessä yhtälaisia, että
> molempien suoritus alkaa jostain kohdasta muistia. Olen myös ymmärtänyt,
> että osoitin funktioon/metodiin on osoitin tuohon kohtaan muistia. Jotta
> kutsussa olisi mieltä, niin osoittimen pitää olla "yhteensopiva"
> määrittelyltään, että parametrit ja paluuarvo saadaan välitettyä.
>
> Jos asia olisi kuten olen ymmärtänyt, niin määrittelyllä ei pitäisi
> käsittääkseni olla eroa. Olen siis ymmärtänyt jotain väärin.
C++:ssa jokaisella luokan ei-staattisella funktiolla on olemassa
esittelemätön 'this' -osoitin joka osoittaa luokan instanssiin. Joten
kun kutsutaan ei-staattinen funktio, on sille annettava 'jollain
tavalla' myös this -osoitin, muutenhan funktio ei osaisi tietää missä
kohti muistia this->data olisi. Kun funktio kutsutaan osoittimen tai
viitteen kautta, välitetään this:nä osoittimen arvo.
Jos luokan funktion osoite otetaan, ei voida enää päätellä mikä osoite
pitää välittää this:nä, ja tämän takia siinä tarvitaankin tuota syktaktista
sokeria.
--
Psi -- <http://www.iki.fi/pasi.savolainen>
Sain meriselityksenä, että kääntäjä kääntää metodin void A::metodi() muotoon
void metodi_A(A *this) (tjsp.) eli parametrilistaan lisätään "salainen"
osoitin kyseiseen luokkaan ja sille annetaan arvoksi se instanssi, mikä
metodin osoittimeen viisaa. Jos siis on void (A::*p)(), a = new A(), p =
&A::metodi ja myöhemmin (a.*p)();, niin itseasiassa metodi esitellään void
metodi_A(A *this) ja p:n esittelyssä tarvitaan A:: jotta tiedetään p:n
ottavan parametrinaan osoittimeen johonkin A:n instanssiin. Kutsussa
(a.*p)(); sitten tuupataan parametriksi &a josta saadaan siis käyttöön
*this.
Jokaisella metodilla pitää olla käsissään *this josta viittaukset
luokkainstanssiin onnistuvat. Ilmeisesti kääntäjä sitten katselee metodeista
esittelemättömät muuttujat ja tutkii onko luokassa sellaisia ja "asettaa
this:n" siihen eteen jos on, noin kansan kielellä.(?)
Käsittääkseni tämä käy yhteen sen kanssa mitä tarkoitit? Mistä löytyisi
saamalleni selitykselle varmennus tai teilaus? En keksinyt googleen sopivaa
hakua (yleensä sivuilla kerrotaan kyllä miten, mutta ei miksi).
> C++:ssa myös vapaan funktion tapauksessa voi käyttää
> &-operaattoria.
Kuten C:ssäkin.
Jep.
Tuon this-parametrin lisäksi assembly-tasolla jokainen metodinimi sisältää
myös luokan nimen, jotta kahdella eri luokalla voisi olla samanniminen
metodi. Lisäksi C++:ssa assembly-tasolla sekä funktion että metodin nimeen
koodataan vielä kaikkien parametrien tyyppi, jotta samannimisia funktioita
voi olla useita (kuormittaminen).
Käytännössä assembly-tasolla funktioiden nimi koodataan todella tiiviiksi,
koska template-ohjelmoinnissa (mm. STL:n käytössä) funktioiden ja metodien
nimistä voi tulla todella pitkiä. Itanium APIa noudattavissa kääntäjissä
(mm. gcc) tuon assembly-tason nimen voi mistä tahansa asiasta saada
selville, jos se kiinnostaa. Vastaavasti assembly-tason nimen voi muuntaa
takaisin ihmisen luettavaan muotoon:
#include <iostream>
#include <ostream>
#include <typeinfo>
#include <string>
#include <cxxabi.h>
class X
{
public:
void metodi(int i, float f, X* p) const;
};
void X::metodi(int, float, X*) const
{
}
int main()
{
void (X::*mp)(int, float, X*) const = &X::metodi;
std::string assembly_nimi = typeid(mp).name();
std::cout << assembly_nimi << std::endl;
std::cout << abi::__cxa_demangle(assembly_nimi.c_str(), 0, 0, 0)
<< std::endl;
}
Metodiosoittimet ovat vielä siitä vinkeitä, että ne eivät voi olla pelkkiä
metodin koodin alkuosoitteita. Nimittäin jos metodi on virtuaalinen, ei
metodin koodin osoitetta tiedetä, koska valittava metodi riippuu kutsun
kohteena olevan olion todellisesta tyypistä. Tällöin koodiosoitteen sijaan
täytyykin tallettaa metodin indeksi luokan virtuaalitaulussa. Käytännössä
siis metodiosoittimen pitää olla lippu + koodiosoitin/virtuaalitauluindeksi.
Käytännössä tämä tarkoittaa, että metodiosoittimen koko on usein suurempi
kuin tavallisen funktio-osoittimen (tai dataosoittimen). Esim. gcc:ssä
metodiosoittin on 64-bittinen myös 32-bittisessä arkkitehtuurissa.
Jos konearkkitehtuurissa tiedetään, että kaikki funktiot alkavat
parillisesta muistiosoitteesta (yleensä pätee), niin silloin
metodiosoittimen vähiten merkitsevää bittiä voidaan periaatteessa käyttää
lippuna. Jos se on 0, metodiosoitin osoittaa suoraan ei-virtuaalisen metodin
koodiin. Jos se on 1, loput metodiosoittimen biteistä ovat indeksi
virtuaalitauluun. Toisaalta tämä tapa on aika hidas, joten esim. gcc ei
käytä sitä.
--
------------- Matti Rintala ------------ matti....@tut.fi ------------
Painting is the art of inclusion. Photography is an art of exclusion.
Jep.
Tulostus:
M1XKFvifPS_E
void (X::*)(int, float, X*) const
Jep.
Tuon this-parametrin lisäksi assembly-tasolla jokainen metodinimi sisältää
myös luokan nimen, jotta kahdella eri luokalla voisi olla samanniminen
metodi. Lisäksi C++:ssa assembly-tasolla sekä funktion että metodin nimeen
koodataan vielä kaikkien parametrien tyyppi, jotta samannimisia funktioita
voi olla useita (kuormittaminen). Ja näiden lisäksi funktioiden ja metodien
nimiin pitää koodata käytetty nimiavaruus tai nimiavaruudet.
Käytännössä assembly-tasolla funktioiden nimi koodataan tiiviimmäksi,
koska template-ohjelmoinnissa (mm. STL:n käytössä) funktioiden ja metodien
nimistä voi tulla todella pitkiä. Esim. Itanium C++ ABIssa
(http://www.codesourcery.com/cxx-abi/abi.html) metodin nimi muodostuu
seuraavaksi (esim. gcc toteuttaa Itanium ABIn):
class X
{
public:
void metodi(int i, float f, X* p) const;
};
Assembly-tasolla: _GLOBAL__I__ZNK1X6metodiEifPS_
Hyvä, nyt saatan jopa muistaa syntaksin kun sain selityksen logiikalle :)
Vielä olisi yksi kysymys. Olettaen, että tiedän binäärissä olevan
funktioita, data-alueita yms. (oikeastaan kaikki tietämäni tulikin jo
esiin), niin mikä kirja selittäisi syvällisemmin mitä suorituksessa
tapahtuu? Haluaisin siis saada tuntumaa siihen mitä binäärit ovat syöneet ja
mitä niiden ajossa tarkemmin ottaen tapahtuu.
Kiitos kaikille vastaajille!
Sellainen kirja kuin Inside the C++ object model (Lippman) kertoo aika
paljon nimeenomaan C++-kielestä. Tämä kirja ei toki sitoudu yhteenkään
konearkkitehtuuriin vaan kertoo nimenomaan kielen oliomallista.
Tietenkin myös siitä, miten ne toteutetaan oikeissa arkkitehtuureissa.
Jos taas haluat tietää tarkemmin binäärin data-alueista, ym. niin
silloin täytyy valita kirja, joka kertoo omasta arkkitehtuuristasi. Jos
se on esim. X86-linux, niin ehkä löytyy joku ELF:istä kertova kirja tai
verkkosivu? Ja varmaan X86-windowsille on omansa.
Hannu
Laitan nimet korvan taakse (vai korvien kun niitä on kaksi?). Tuo Lippman
voisi vastata tarpeita aika hyvin.
Arkkitehtuurista en osaa sanoa, kun en ole vielä töitä löytänyt :) Olen
muutama vuosi sitten katsellut ELF:istä kertovia nettikirjoja ja ne olivat
suht epäsopivia asian opiskeluun, tilanne voi toki sittemmin olla muuttunut.
(Millehän alustalle C/C++-softia väännetään eniten?)
> Jos konearkkitehtuurissa tiedetään, että kaikki funktiot alkavat
> parillisesta muistiosoitteesta (yleensä pätee), niin silloin
> metodiosoittimen vähiten merkitsevää bittiä voidaan periaatteessa käyttää
> lippuna. Jos se on 0, metodiosoitin osoittaa suoraan ei-virtuaalisen metodin
> koodiin. Jos se on 1, loput metodiosoittimen biteistä ovat indeksi
> virtuaalitauluun. Toisaalta tämä tapa on aika hidas, joten esim. gcc ei
> käytä sitä.
Tokkopa tuo tapa ainakaan x86:ssa mitenkään hidas on: koodiin
hypättäessä lippu on 0, joten se ei voi haitata, ja indeksiä
käytettäessäkään ei lippua tarvitse erillisellä käskyllä poistaa,
vaan sen voi kumota lasketusta osoitteesta vähennettävällä
vakiolla. Ja niinpä GCC käyttääkin tätä tapaa; mutta
metodiosoitin ei siltikään mahdu 32 bittiin, koska siihen on
koodattu myös tieto, mitä moniperineen olion osista käytetään.
class c {
int f1();
int f2();
virtual int v1();
virtual int v2();
};
int x(c *p, int (c::*m)())
{
return (p->*m)();
}
Yllä olevan koodin gcc -O2 -S kääntää tämmöiseksi (lisäsin kommentit):
.file "virtual.cc"
.text
.align 2
.p2align 4,,15
.globl _Z1xP1cMS_FivE
.type _Z1xP1cMS_FivE, @function
_Z1xP1cMS_FivE:
.LFB2:
pushl %ebp ; perusta pinokehys
.LCFI0:
movl %esp, %ebp
.LCFI1:
subl $8, %esp ; varaa tilaa pinosta
.LCFI2:
movl %ebx, (%esp) ; vanha %ebx talteen
.LCFI3:
movl %esi, 4(%esp) ; vanha %esi talteen
.LCFI4:
movl 12(%ebp), %eax ; %eax = m.__funktio
movl 16(%ebp), %edx ; %edx = m.__osaolio
movl 8(%ebp), %esi ; %esi = p
testb $1, %al ; onkos m.__funktio & 1 päällä vai ei
movl %eax, %ecx ; %ecx = m.__funktio
movl %edx, %ebx ; %ebx = m.__osaolio
leal (%esi,%edx), %edx ; %edx = (char *) p + (ptrdiff_t) m.__osaolio
je .L4 ; hyppää jos m.__funktio & 1 ei ole päällä
leal (%esi,%ebx), %edx ; %edx = (char *) p + (ptrdiff_t) m.__osaolio
; (häh? äskenhän sama arvo jo laskettiin!)
movl (%edx), %eax ; %eax = p:n jonkin virtuaalitaulun osoite
movl -1(%eax,%ecx), %ecx ; %ecx = halutun funktion osoite
.L4:
movl %edx, 8(%ebp) ; aseta kutsuttavan olion osoite parametriksi;
; se ei aina ole sama kuin tämän funktion p
movl (%esp), %ebx ; vanha %ebx takaisin
movl 4(%esp), %esi ; vanha %esi takaisin
movl %ebp, %esp ; pura pinokehys
popl %ebp
jmp *%ecx ; hyppää funktioon p->*m
.LFE2:
.size _Z1xP1cMS_FivE, .-_Z1xP1cMS_FivE
.ident "GCC: (GNU) 4.0.4 20060507 (prerelease) (Debian 4.0.3-3)"
.section .note.GNU-stack,"",@progbits
Ok, kiitos. En ehtinyt tarkastamaan tuota yksityiskohtaa, kun huomasin että
sizeof(metodiosoitin) on 8 tavua. Ihmettelinkin, miksi tuota bittikoodausta
ei käytettäisi, mutta unohdin taas moniperiytymisen vaikutuksen. :)
Mikä siinä on hidasta?
> joten esim. gcc ei käytä sitä.
Näyttäisi käyttävän, jos arkkitehtuuri sallii.
<URL:http://gcc.gnu.org/onlinedocs/gccint/Type-Layout.html>
(Etsi vtable.)
--
Timo Korvola <URL:http://www.iki.fi/tkorvola>
>
> (Millehän alustalle C/C++-softia väännetään eniten?)
>
Harmillisesti vaikuttaa melkein, että Symbianille.
Itsellä on 14v C++-kokemusta, mutta tahtoo olla java tai c# ne kielet
nykyään minkä osaajia kaivataan.
--
There is a fly on your nose.