virtual == zawsze runtime?

0

Wydaje mi się to być oczywiste ale pytam dla pewności.

class A
{
public:
	virtual void a()
	{
		std::cout << "A";
	}
};

class B : public A
{
public:
	virtual void a() override
	{
		std::cout << "B";
	}
};

int main()
{
	
	B b;
	b.a();


	A* a = new B;
	a->a();

}

Czy pierwsze wywołanie ma taki sam koszt jak gdyby metoda nie była virtualna?

  • Czy da się za drugim wywołaniem ominąć runtime poliformizm?
2
  1. Tak, bo masz tu wczesne wiązanie.
  2. Da się, postępując tak jak w przypadku pierwszym czyli stosując wczesne wiązanie. W skrócie: nie używając wskaźnika ani referencji.
3

Dodam tylko, że kompilator potrafi zrobić coś, co nazywa się "dewirtualizacją" - tzn. wykryć miejsca, gdzie wywołania wirtualne nie są potrzebne bo typ wskaźnika/referencji można ustalić i wywołać funkcję "normalnie".

5

Czy pierwsze wywołanie ma taki sam koszt jak gdyby metoda nie była virtualna?

Czy drugie wywołanie ma taki sam koszt jak gdyby metoda nie była virtualna?

(niezadane pytanie)
To zależy od kompilatora. Przykładowo, co robi z tym cygwinowy g++ (akurat mam pod ręką) (skrócone):

.text:004017BE                 mov     [esp+10h+var_8], 1
.text:004017C6                 mov     [esp+10h+var_C], offset aB ; "B"
.text:004017CE                 mov     [esp+10h+var_10], offset std::cout
.text:004017D5                 call    std::__ostream_insert<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const*,int) ; Wypisanie B
.text:004017DA                 mov     [esp+10h+var_10], 4
.text:004017E1                 call    __wrap__Znwj ; "coś tam"
.text:004017E6                 mov     dword ptr [eax], offset off_4030A0
.text:004017EC                 mov     [esp+10h+var_10], eax
.text:004017EF                 call    sub_401760 ; wywołanie a()
.text:004017F4                 xor     eax, eax
.text:004017F6                 leave
.text:004017F7                 retn

...

.text:00401760 sub_401760      proc near
.text:00401760                 sub     esp, 1Ch
.text:00401763                 mov     [esp+1Ch+var_14], 1
.text:0040176B                 mov     [esp+1Ch+var_18], offset aB ; "B"
.text:00401773                 mov     [esp+1Ch+var_1C], offset std::cout
.text:0040177A                 call    std::__ostream_insert<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const*,int) ; Wypisanie B
.text:0040177F                 add     esp, 1Ch
.text:00401782                 retn
.text:00401782 sub_401760      endp

Czyli pierwsze wywołanie zostało zinlinowane (ok), a drugie nie zostało zinlinowane, ale również nie jest wirtualne (czyli półśrodek). Tak czy inaczej, nie płacisz za wywołane wirtualne w tym przypadku, bo kompilator może się domyślić jaki typ ma obiekt w tym momencie.
(chciałem sprawdzić jak się zachowa clang, ale na windowsie coś nie chce chodzić ładnie).

W ogólności w drugim przypadku wszystko będzie zależało od tego jak inteligentnie się zachowa kompilator.

  • Czy da się za drugim wywołaniem ominąć runtime poliformizm?

Poza liczeniem na kompilator? Niezbyt.
Zależnie do czego tego potrzebujesz (i czy naprawdę warto), możesz spróbować zastosować CRTP (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern).

0

Co do drugiego wywołania, co jeśli bym je przerzutował za pomocą static_cast?

0

f5, czy w ten sposób nastąpi wczesne wiązanie i tym samym ominę runtime ?

 
    A* a = new B;
static_cast<B*>(a)->a();

1 użytkowników online, w tym zalogowanych: 0, gości: 1