Consider the following snippet of code:
#include <new>
#include <iostream>
struct IDivideResult {
virtual int result() = 0;
virtual int remainder() = 0;
};
struct DivideResult : IDivideResult {
DivideResult(int result, int remainder) : result_(result), remainder_(remainder) {}
int result() override { return result_; }
int remainder() override { return remainder_; }
int result_, remainder_;
};
struct LazyDivideResult : IDivideResult {
LazyDivideResult(int dividend, int divisor) : dividend_(dividend), divisor_(divisor) {}
int result() override { return Transmogrify()->result(); }
int remainder() override { return Transmogrify()->remainder(); }
DivideResult *Transmogrify() {
int result = dividend_ / divisor_;
int remainder = dividend_ % divisor_;
return new (this) DivideResult(result, remainder);
}
int dividend_, divisor_;
};
void Print(IDivideResult *div) {
int result = div->result();
int remainder = div->remainder();
std::cout << result << " " << remainder << "\n";
}
int main() {
IDivideResult *div = new LazyDivideResult(10, 3);
Print(div);
}
The question I have is about behavior of Print
function.
Expected behavior: after calling result()
div points to instance of DivideResult
class, calling remainder()
function calls DivideResult::remainder()
function.
Possible (?) behavior: at the moment of calling result()
pointer to vtable
of LazyDivideResult
is cached. The next call to remainder()
reuses previously cached pointer to vtable
and hence calls LazyDivideResult::remainder()
.
I've heard that virtual tables are not part of C++ standard. Also disassembly of clang/gcc/msvc generated code renders expected behavior.
So question here is: is compiler allowed to generate code that leads to "possible behavior" described above? Are there any guarantees?