执行GCC';s函数_属性_是否与虚拟函数一起工作? < > GCC C++编译器通过一个扩展名,例如: int square(int) __attribute__((const));

执行GCC';s函数_属性_是否与虚拟函数一起工作? < > GCC C++编译器通过一个扩展名,例如: int square(int) __attribute__((const));,c++,gcc,pure-virtual,C++,Gcc,Pure Virtual,特别是两个属性,const和pure,允许您声明函数的求值没有副作用,只取决于其参数(const),或者只取决于其参数和全局变量(pure)。这允许消除公共子表达式,这可能会导致调用此类函数的次数少于在代码中编写的次数 我的问题是,这是否可以安全、正确和合理地用于虚拟成员功能: struct Foo { virtual int square(int) __attribute__((pure)); // does that make sense? }; 这有什么合理的语义吗?这是允许

特别是两个属性,
const
pure
,允许您声明函数的求值没有副作用,只取决于其参数(
const
),或者只取决于其参数和全局变量(
pure
)。这允许消除公共子表达式,这可能会导致调用此类函数的次数少于在代码中编写的次数

我的问题是,这是否可以安全、正确和合理地用于虚拟成员功能:

struct Foo
{
    virtual int square(int) __attribute__((pure));   // does that make sense?
};
这有什么合理的语义吗?这是允许的吗?还是只是被忽略了?恐怕我在GCC文档中找不到这个问题的答案


出现此问题的原因是有一系列编译器选项
-Wsuggest attribute
,这些选项使GCC提供建议,说明可以将这些属性放置在何处以改进代码。然而,即使对于虚拟函数,它似乎也会提出这些建议,我想知道是否应该认真对待这些建议。

在您链接到的文档中,const属性的描述下有以下注释:

请注意,具有指针参数并检查指向的数据的函数不能声明为const

我认为这包括成员函数,因为它们有一个隐式指针参数(至少虚拟函数需要检查它才能到达vtable,不是吗?)


他们似乎在这个线程中得出了类似的结论:

GCC允许并接受它。它通常不会被忽略(您知道这一点,因为GCC总是输出
警告:attribute ignored
,当它完全忽略一个属性时,它不会在这里)。还要读最后一段

这是否合理是另一个问题。虚拟函数可以重载,您可以在不使用属性的情况下重载它。这开启了下一个问题:这合法吗

人们期望具有不同属性的函数具有不同的签名(例如使用
const
限定符或不同的异常规范),但事实并非如此。GCC认为它们在这方面完全相同。您可以通过从
Foo
派生
Bar
并实现成员函数non-const来验证这一点。然后

decltype(&Bar::square) f1 = &Foo::square;
decltype(&Foo::square) f2 = &Bar::square;
将在第二行中给出编译时错误,但在第一行中没有,正如您所期望的那样。如果签名不同(请尝试限定函数const,而不是使用属性!),则第一行已经给出了一个错误

最后,它安全吗?它有意义吗?它总是安全的,编译器必须确保它是安全的。它在语义上是有意义的,在一定范围内

从语义的角度来看,如果函数是纯函数,那么声明函数
const
pure
是“正确的”。然而,如果你向界面的用户做出一个可能不真实的“承诺”,那就有点尴尬了。有人可能会调用这个函数,它在派生类上的所有外观都是
const
,而这不是真的。编译器必须确保它仍然有效,但用户对性能的期望可能与实际情况不同

将函数标记为
const
pure
可能使编译器能够更好地优化。现在,对于虚函数,这有点困难,因为对象可能是派生类型,而这不是真的

这必然意味着编译器必须忽略优化属性,除非虚拟调用可以静态解析。这可能仍然经常发生,但不是一般情况。

第一个问题是这些属性是否对虚拟方法具有有效的语义。在我看来他们是这样的。我认为,如果虚拟函数被标记为纯函数,那么您将向编译器承诺,所有实现都只依赖于它们的参数和全局内存中的数据(并且不改变它们),全局内存中的数据也将包括对象的内容。如果虚拟函数被标记为const,这意味着它只能依赖于它的参数,甚至不允许它检查对象的内容。编译器必须强制所有重写虚拟方法声明的属性至少与其父方法一样强

下一个问题是GCC是否使用这些属性进行优化。在下面的测试程序中,您可以看到版本4.6.3没有(尝试使用-O3编译到assembler,您将看到循环被展开)

结构A{ 虚拟整数常数f(整数x)uuu属性uuu((常数))=0; }; int do_stuff(A*A){ int b=0; 对于(int i=0;iconst_f(0); } 返回b; } 即使在编译时已知类型的以下程序中,编译器也不会优化循环

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;
}
结构A{ 虚拟整数常数f(整数x)uuu属性uuu((常数))=0; }; 结构B:公共A{ int const_f(int x)__属性__((const)); }; int do_材料(B*B){ int c=0; 对于(int i=0;iconst_f(0); } 返回c; } 从中删除继承(从而使方法非虚拟)允许编译器进行预期的优化


没有关于这些属性的标准或文档,因此我们能得到的最佳参考是实现。由于它们目前没有效果,我建议避免在虚拟方法上使用它们,以防将来行为发生意外变化。

G++4.8.1似乎尊重
const
当且仅当通过静态绑定调用函数时,虚拟成员函数上的函数属性

给定以下源代码:

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
}
…编译器生成以下(摘录的)汇编代码:

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
}
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)
}