tow*_*owi 29 c++ compiler-construction final c++11
C++ 11将允许将类和虚方法标记为最终,以禁止从它们派生或覆盖它们.
class Driver {
virtual void print() const;
};
class KeyboardDriver : public Driver {
void print(int) const final;
};
class MouseDriver final : public Driver {
void print(int) const;
};
class Data final {
int values_;
};
Run Code Online (Sandbox Code Playgroud)
这非常有用,因为它告诉读者接口有关使用此类/方法的意图.如果用户尝试覆盖,则用户获得诊断也可能有用.
但编译器的观点是否有优势?当编译器知道"这个类永远不会从中派生出来"或"这个虚拟函数永远不会被覆盖"时,编译器能做些什么吗?
因为final
我主要发现只有N2751指的是它.通过一些讨论,我发现了来自C++/CLI方面的论据,但没有明确暗示为什么final
对编译器有用.我正在考虑这个问题,因为我也看到了标记类的一些缺点final
:要对受保护的成员函数进行单元测试,可以派生一个类并插入测试代码.有时这些课程是很好的候选人final
.在这些情况下,这种技术是不可能的.
Fle*_*exo 36
我可以想到一个从优化角度来看它可能对编译器有帮助的场景.我不确定编译器实现者是否值得努力,但理论上它至少是可行的.
通过virtual
对派生final
类型的调用分派,您可以确保没有其他任何内容派生自该类型.这意味着(至少在理论上)final
关键字可以virtual
在编译时正确地解析一些调用,这将使得一些优化成为可能,否则在virtual
调用时是不可能的.
例如,如果你有delete most_derived_ptr
,most_derived_ptr
指向派生final
类型的指针,则编译器可以简化对virtual
析构函数的调用.
同样,对于virtual
引用/指向最派生类型的成员函数的调用.
如果有任何编译器今天这样做,我会感到非常惊讶,但它似乎可能会在未来十年左右实现.
也可能有在能够推断出(在没有一些millage friend
S)标记的东西protected
在final
class
也有效地成为private
.
Mat*_* M. 30
对函数的虚拟调用比正常调用稍微昂贵一些.除了实际执行调用之外,运行时必须首先确定要调用哪个函数,哪些函数会导致:
与直接调用(其中函数的地址事先已知(并且用符号硬编码))相比,这导致了小的开销.好的编译器设法使其比常规调用慢10%-15%,如果函数有任何关系,这通常是微不足道的.
编译器的优化器仍然试图避免各种开销,而虚函数函数调用通常是一个悬而未决的结果.例如,请参阅C++ 03:
struct Base { virtual ~Base(); };
struct Derived: Base { virtual ~Derived(); };
void foo() {
Derived d; (void)d;
}
Run Code Online (Sandbox Code Playgroud)
Clang得到:
define void @foo()() {
; Allocate and initialize `d`
%d = alloca i8**, align 8
%tmpcast = bitcast i8*** %d to %struct.Derived*
store i8** getelementptr inbounds ([4 x i8*]* @vtable for Derived, i64 0, i64 2), i8*** %d, align 8
; Call `d`'s destructor
call void @Derived::~Derived()(%struct.Derived* %tmpcast)
ret void
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,编译器已经足够智能,以确定d
是一个Derived
那么没有必要承担虚拟调用的开销.
实际上,它会很好地优化以下功能:
void bar() {
Base* b = new Derived();
delete b;
}
Run Code Online (Sandbox Code Playgroud)
但是在某些情况下编译器无法得出这样的结论:
Derived* newDerived();
void deleteDerived(Derived* d) { delete d; }
Run Code Online (Sandbox Code Playgroud)
在这里,我们可以(天真地)期望调用deleteDerived(newDerived());
将导致与之前相同的代码.但事实并非如此:
define void @foobar()() {
%1 = tail call %struct.Derived* @newDerived()()
%2 = icmp eq %struct.Derived* %1, null
br i1 %2, label %_Z13deleteDerivedP7Derived.exit, label %3
; <label>:3 ; preds = %0
%4 = bitcast %struct.Derived* %1 to void (%struct.Derived*)***
%5 = load void (%struct.Derived*)*** %4, align 8
%6 = getelementptr inbounds void (%struct.Derived*)** %5, i64 1
%7 = load void (%struct.Derived*)** %6, align 8
tail call void %7(%struct.Derived* %1)
br label %_Z13deleteDerivedP7Derived.exit
_Z13deleteDerivedP7Derived.exit: ; preds = %3, %0
ret void
}
Run Code Online (Sandbox Code Playgroud)
公约可以规定newDerived
返回a Derived
,但编译器不能做出这样的假设:如果它返回了进一步派生的内容呢?这样的话你能看到所有的丑陋的机械参与检索v表指针,选择在表中相应的条目,并最终进行呼叫.
但是如果我们输入final
in,那么我们给编译器保证它不能是其他任何东西:
define void @deleteDerived2(Derived2*)(%struct.Derived2* %d) {
%1 = icmp eq %struct.Derived2* %d, null
br i1 %1, label %4, label %2
; <label>:2 ; preds = %0
%3 = bitcast i8* %1 to %struct.Derived2*
tail call void @Derived2::~Derived2()(%struct.Derived2* %3)
br label %4
; <label>:4 ; preds = %2, %0
ret void
}
Run Code Online (Sandbox Code Playgroud)
简而言之:final
允许编译器在无法检测到相关函数的情况下避免虚拟调用的开销.
归档时间: |
|
查看次数: |
7294 次 |
最近记录: |