The GCC C++ compiler offers a family of extensions via function attributes, such as:
int square(int) __attribute__((const));
Two attributes in particular, const
and pure
, allow you to declare that a function's evaluation has no side effects and depends only on its arguments (const
), or only on its arguments and global variables (pure
). This allows for common subexpression elimination, which may have the effect that such a function gets called fewer times than it is written in the code.
My question is whether this can be used safely, correctly and sensibly for virtual member functions:
struct Foo
{
virtual int square(int) __attribute__((pure)); // does that make sense?
};
Does this have any sensible semantics? Is it allowed at all? Or is it just ignored? I'm afraid I can't find the answer to this in the GCC documentation.
The reason for this question is that there is a family of compiler options -Wsuggest-attribute
which make GCC produce suggestions of where those attributes might be placed to improve the code. However, it seems to end up making these suggestions even for virtual functions, and I wonder whether those suggestions should be taken seriously.
A virtual function is a member function that you expect to be redefined in derived classes. When you refer to a derived class object using a pointer or a reference to the base class, you can call a virtual function for that object and execute the derived class's version of the function.
Rules for Virtual FunctionsVirtual functions cannot be static. A virtual function can be a friend function of another class. Virtual functions should be accessed using pointer or reference of base class type to achieve runtime polymorphism.
The __attribute__ directive is used to decorate a code declaration in C, C++ and Objective-C programming languages. This gives the declared code additional attributes that would help the compiler incorporate optimizations or elicit useful warnings to the consumer of that code.
In C++, a static member function of a class cannot be virtual. Virtual functions are invoked when you have a pointer or reference to an instance of a class. Static functions aren't tied to the instance of a class but they are tied to the class.
G++ 4.8.1 appears to respect the pure
and const
function attributes on virtual member functions if and only if the function is called via a static binding.
Given the following source code:
struct Base {
void w();
void x() __attribute__ ((const));
virtual void y();
virtual void z() __attribute__ ((const));
};
struct Derived : public Base {
void w() __attribute__ ((const));
void x();
virtual void y() __attribute__ ((const));
virtual void z();
};
void example() {
Base b, *pb;
Derived d, *pd;
b.w(); // called
b.x(); // not called
b.y(); // called
b.z(); // not called
pb->w(); // called
pb->x(); // not called
pb->y(); // called
pb->z(); // called
d.w(); // not called
d.x(); // called
d.y(); // not called
d.z(); // called
pd->w(); // not called
pd->x(); // called
pd->y(); // called
pd->z(); // called
}
…the compiler produces the following (excerpted) assembly code:
void example() {
Base b, *pb;
Derived d, *pd;
b.w(); // called
1c: e8 00 00 00 00 callq 21 <_Z7examplev+0x21>
b.x(); // not called
b.y(); // called
21: 48 89 e7 mov %rsp,%rdi
24: e8 00 00 00 00 callq 29 <_Z7examplev+0x29>
b.z(); // not called
pb->w(); // called
29: 48 89 df mov %rbx,%rdi
2c: e8 00 00 00 00 callq 31 <_Z7examplev+0x31>
pb->x(); // not called
pb->y(); // called
31: 48 8b 2b mov (%rbx),%rbp
34: 48 89 df mov %rbx,%rdi
37: ff 55 00 callq *0x0(%rbp)
pb->z(); // called
3a: 48 89 df mov %rbx,%rdi
3d: ff 55 08 callq *0x8(%rbp)
d.w(); // not called
d.x(); // called
40: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
45: e8 00 00 00 00 callq 4a <_Z7examplev+0x4a>
d.y(); // not called
d.z(); // called
4a: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
4f: e8 00 00 00 00 callq 54 <_Z7examplev+0x54>
pd->w(); // not called
pd->x(); // called
54: 48 89 df mov %rbx,%rdi
57: e8 00 00 00 00 callq 5c <_Z7examplev+0x5c>
pd->y(); // called
5c: 48 8b 2b mov (%rbx),%rbp
5f: 48 89 df mov %rbx,%rdi
62: ff 55 00 callq *0x0(%rbp)
pd->z(); // called
65: 48 89 df mov %rbx,%rdi
68: ff 55 08 callq *0x8(%rbp)
}
The first question is whether these attributes even have valid semantics for virtual methods. In my opinion they do. I would expect that if a virtual function was labelled pure you would be promising the compiler that all implementations only rely on their arguments and the data in global memory (and do not change that), where data in global memory would also include the contents of the object. If a virtual function were labelled const this would mean it could depend on only on its arguments, it would not even be allowed to inspect the contents of the object. The compiler would have to enforce that all overriding virtual methods declare attributes at least as strong as their parents.
The next question is whether GCC uses these attributes to make optimisations. In the following test program you can see that version 4.6.3 does not (try compiling to assembler with -O3 and you will see the loop is unrolled).
struct A {
virtual int const_f(int x) __attribute__((const)) = 0;
};
int do_stuff(A *a) {
int b = 0;
for (int i=0; i<10; i++) {
b += a->const_f(0);
}
return b;
}
Even in the following program where the type is known at compile time the compiler does not optimize the loop.
struct A {
virtual int const_f(int x) __attribute__((const)) = 0;
};
struct B : public A {
int const_f(int x) __attribute__((const));
};
int do_stuff(B *b) {
int c = 0;
for (int i=0; i<10; i++) {
c += b->const_f(0);
}
return c;
}
Removing the inheritance from A (and thus making the method non-virtual) allows the compiler to make the expected optimisation.
There is no standard or documentation regarding these attributes, so the best reference we can have is the implementation. As they currently have no effect I would suggest avoiding their use on virtual methods in case the behaviour changes unexpectedly in the future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With