วัตถุประสงค์ของfinal
คำสำคัญใน C ++ 11 สำหรับฟังก์ชั่นคืออะไร? ฉันเข้าใจว่ามันป้องกันการแทนที่ฟังก์ชั่นโดยคลาสที่ได้รับ แต่ถ้าเป็นกรณีนี้มันไม่เพียงพอที่จะประกาศว่าไม่ใช่final
ฟังก์ชันของคุณใช่หรือไม่ มีสิ่งอื่นอีกไหมที่ฉันหายไปที่นี่
วัตถุประสงค์ของfinal
คำสำคัญใน C ++ 11 สำหรับฟังก์ชั่นคืออะไร? ฉันเข้าใจว่ามันป้องกันการแทนที่ฟังก์ชั่นโดยคลาสที่ได้รับ แต่ถ้าเป็นกรณีนี้มันไม่เพียงพอที่จะประกาศว่าไม่ใช่final
ฟังก์ชันของคุณใช่หรือไม่ มีสิ่งอื่นอีกไหมที่ฉันหายไปที่นี่
คำตอบ:
สิ่งที่คุณขาดหายไปดังที่ idljarn ได้กล่าวไว้แล้วในความคิดเห็นคือถ้าคุณแทนที่ฟังก์ชันจากคลาสพื้นฐานคุณจะไม่สามารถทำเครื่องหมายว่าไม่ใช่เสมือน:
struct base {
virtual void f();
};
struct derived : base {
void f() final; // virtual as it overrides base::f
};
struct mostderived : derived {
//void f(); // error: cannot override!
};
virtual
สาเหตุอาจทำให้เกิดข้อผิดพลาดและ C ++ 11 เพิ่มoverride
แท็กลงในฟังก์ชันที่จะตรวจจับสถานการณ์นั้นและไม่สามารถคอมไพล์เมื่อฟังก์ชันที่ตั้งใจจะแทนที่จริง ๆซ่อน
เป็นการป้องกันคลาสจากการสืบทอด จากWikipedia :
C ++ 11 ยังเพิ่มความสามารถในการป้องกันการสืบทอดจากคลาสหรือเพียงแค่ป้องกันวิธีการแทนที่ในคลาสที่ได้รับ สิ่งนี้ทำกับตัวระบุพิเศษขั้นสุดท้าย ตัวอย่างเช่น:
struct Base1 final { }; struct Derived1 : Base1 { }; // ill-formed because the class Base1 // has been marked final
มันถูกใช้เพื่อทำเครื่องหมายฟังก์ชันเสมือนเพื่อป้องกันไม่ให้ถูกเขียนทับในคลาสที่ได้รับ:
struct Base2 { virtual void f() final; }; struct Derived2 : Base2 { void f(); // ill-formed because the virtual function Base2::f has // been marked final };
วิกิพีเดียยังทำให้ประเด็นที่น่าสนใจอีก :
โปรดทราบว่าค่า
override
มิได้final
เป็นคำหลักภาษา มันเป็นตัวบ่งชี้ทางเทคนิค พวกเขาได้รับความหมายพิเศษเมื่อใช้ในบริบทเฉพาะเหล่านั้นเท่านั้น ในตำแหน่งอื่น ๆ พวกเขาสามารถเป็นตัวระบุที่ถูกต้อง
หมายความว่าอนุญาตสิ่งต่อไปนี้:
int const final = 0; // ok
int const override = 1; // ok
"ขั้นสุดท้าย" ยังช่วยให้การเพิ่มประสิทธิภาพคอมไพเลอร์เพื่อหลีกเลี่ยงการโทรทางอ้อม:
class IAbstract
{
public:
virtual void DoSomething() = 0;
};
class CDerived : public IAbstract
{
void DoSomething() final { m_x = 1 ; }
void Blah( void ) { DoSomething(); }
};
ด้วย "final" คอมไพเลอร์สามารถโทรCDerived::DoSomething()
โดยตรงจากภายในBlah()
หรือแม้แต่อินไลน์ ไม่ว่าจะมีการสร้างทางอ้อมโทรภายในBlah()
เพราะอาจเรียกได้ว่าอยู่ในชั้นเรียนมาแทนที่ซึ่งมีBlah()
DoSomething()
ไม่มีอะไรที่จะเพิ่มในด้านความหมายของ "ขั้นสุดท้าย"
แต่ฉันต้องการเพิ่มความคิดเห็นของคริสกรีนว่า "ขั้นสุดท้าย" อาจกลายเป็นเทคนิคการปรับให้เหมาะสมที่สำคัญที่สุดในอนาคตอันใกล้ ไม่เพียง แต่ในกรณีง่าย ๆ ที่เขาพูดถึง แต่ยังรวมถึงลำดับชั้นของโลกแห่งความจริงที่ซับซ้อนซึ่งสามารถ "ปิด" โดย "ขั้นสุดท้าย" ดังนั้นการอนุญาตให้คอมไพเลอร์ในการสร้างรหัสการส่งที่มีประสิทธิภาพมากกว่าด้วยวิธี vtable ปกติ
ข้อเสียอย่างหนึ่งที่สำคัญของ vtables คือสำหรับวัตถุเสมือนใด ๆ (สมมติว่า 64- บิตบน Intel CPU ทั่วไป) ตัวชี้เพียงอย่างเดียวกินได้ถึง 25% (8 จาก 64 ไบต์) ของสายแคช ในแอพพลิเคชั่นที่ฉันชอบเขียนมันเจ็บมาก (และจากประสบการณ์ของฉันมันเป็นอาร์กิวเมนต์ # 1 กับ C ++ จากมุมมองการทำงานที่พิถีพิถันคือโดยโปรแกรมเมอร์ C)
ในแอปพลิเคชันที่ต้องการประสิทธิภาพสูงสุดซึ่งไม่แปลกสำหรับ C ++ นี่อาจกลายเป็นสิ่งที่ยอดเยี่ยมแน่นอนโดยไม่ต้องการแก้ไขปัญหานี้ด้วยตนเองในสไตล์ C หรือการเล่นกลเทมเพลตแปลก ๆ
เทคนิคนี้เป็นที่รู้จักกันDevirtualization คำที่ควรจดจำ :-)
มีคำปราศรัยที่ยิ่งใหญ่เมื่อเร็ว ๆ นี้โดย Andrei Alexandrescu ซึ่งค่อนข้างดีที่อธิบายว่าคุณสามารถแก้ไขสถานการณ์เช่นนี้ได้อย่างไรในวันนี้และวิธี "ขั้นสุดท้าย" อาจเป็นส่วนหนึ่งของการแก้ไขกรณีที่คล้ายกัน "อัตโนมัติ" ในอนาคต (พูดคุยกับผู้ฟัง):
http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly
สุดท้ายไม่สามารถนำไปใช้กับฟังก์ชั่นที่ไม่ใช่เสมือน
error: only virtual member functions can be marked 'final'
มันไม่มีความหมายมากนักที่จะสามารถทำเครื่องหมายวิธีการที่ไม่ใช่เสมือนเป็น 'ขั้นสุดท้าย' ป.ร. ให้ไว้
struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // this will call A :: foo anyway, regardless of whether there is a B::foo
a->foo()
A::foo
มักจะเรียก
แต่ถ้า A :: foo เป็นvirtual
เช่นนั้น B :: foo จะลบล้างมัน สิ่งนี้อาจไม่เป็นที่พึงปรารถนาและด้วยเหตุนี้จึงสมเหตุสมผลที่จะทำให้ฟังก์ชันเสมือนจริงเป็นที่สิ้นสุด
คำถามคือทำไมอนุญาตขั้นสุดท้ายในฟังก์ชั่นเสมือน หากคุณมีลำดับชั้นลึก:
struct A { virtual void foo(); };
struct B : public A { virtual void foo(); };
struct C : public B { virtual void foo() final; };
struct D : public C { /* cannot override foo */ };
จากนั้นfinal
จะวาง 'พื้น' ลงบนจำนวนที่สามารถทำได้ คลาสอื่นสามารถขยาย A และ B และแทนที่ได้foo
แต่คลาสนั้นขยาย C ดังนั้นจึงไม่อนุญาต
ดังนั้นจึงอาจไม่สมเหตุสมผลที่จะทำให้ฟู 'ระดับบนสุด' final
แต่อาจทำให้รู้สึกลดลง
(ฉันคิดว่ามีช่องว่างที่จะขยายคำสุดท้ายและแทนที่สมาชิกที่ไม่ใช่เสมือนพวกเขาอาจมีความหมายที่แตกต่างกันไป)
final
แต่ฉันรู้สึกว่าบางครั้งมันอาจจะทำให้ความรู้สึกที่จะทำให้ฟังก์ชั่นระดับบนสุด ตัวอย่างเช่นถ้าคุณรู้ว่าคุณต้องการทั้งหมดShape
เพื่อfoo()
บางสิ่งบางอย่างที่กำหนดไว้ล่วงหน้าและแน่นอนว่าไม่มีรูปร่างที่ได้มาควรแก้ไข หรือฉันผิดและมีรูปแบบการจ้างงานที่ดีกว่าสำหรับกรณีนี้หรือไม่ แก้ไข: โอ้อาจเป็นเพราะในกรณีนั้นเราไม่ควรfoo()
virtual
เริ่มต้นระดับบนสุดด้วย? แต่ก็ยังสามารถซ่อนได้แม้ว่าจะเรียกอย่างถูกต้อง (polymorphically) ผ่านShape*
...
กรณีการใช้งานสำหรับคำหลัก 'สุดท้าย' ที่ฉันชื่นชอบมีดังนี้:
// This pure abstract interface creates a way
// for unit test suites to stub-out Foo objects
class FooInterface
{
public:
virtual void DoSomething() = 0;
private:
virtual void DoSomethingImpl() = 0;
};
// Implement Non-Virtual Interface Pattern in FooBase using final
// (Alternatively implement the Template Pattern in FooBase using final)
class FooBase : public FooInterface
{
public:
virtual void DoSomething() final { DoFirst(); DoSomethingImpl(); DoLast(); }
private:
virtual void DoSomethingImpl() { /* left for derived classes to customize */ }
void DoFirst(); // no derived customization allowed here
void DoLast(); // no derived customization allowed here either
};
// Feel secure knowing that unit test suites can stub you out at the FooInterface level
// if necessary
// Feel doubly secure knowing that your children cannot violate your Template Pattern
// When DoSomething is called from a FooBase * you know without a doubt that
// DoFirst will execute before DoSomethingImpl, and DoLast will execute after.
class FooDerived : public FooBase
{
private:
virtual void DoSomethingImpl() {/* customize DoSomething at this location */}
};
final
เพิ่มเจตนาชัดเจนที่จะไม่แทนที่ฟังก์ชันของคุณและจะทำให้เกิดข้อผิดพลาดของคอมไพเลอร์หากมีการละเมิด:
struct A {
virtual int foo(); // #1
};
struct B : A {
int foo();
};
ในฐานะที่เป็นรหัสย่อมาก็รวบรวมและแทนที่B::foo
เป็นเสมือนจริงโดยวิธี อย่างไรก็ตามหากเราเปลี่ยน # 1 เป็นนี่เป็นข้อผิดพลาดของคอมไพเลอร์และเราไม่ได้รับอนุญาตให้แทนที่ใด ๆ เพิ่มเติมในชั้นเรียนที่ได้รับA::foo
B::foo
virtual int foo() final
A::foo
โปรดทราบว่าสิ่งนี้ไม่อนุญาตให้เรา "เปิดใหม่" ลำดับชั้นใหม่นั่นคือไม่มีวิธีที่จะสร้างB::foo
ฟังก์ชั่นใหม่ที่ไม่เกี่ยวข้องซึ่งสามารถเป็นอิสระที่หัวของลำดับชั้นเสมือนใหม่ เมื่อฟังก์ชั่นเป็นที่สิ้นสุดก็ไม่สามารถประกาศอีกครั้งในชั้นเรียนที่ได้รับใด ๆ
คำหลักสุดท้ายช่วยให้คุณสามารถประกาศวิธีเสมือนแทนที่มันเป็น N ครั้งแล้วสั่งว่า 'ไม่สามารถแทนที่ได้อีกต่อไป' มันจะมีประโยชน์ในการ จำกัด การใช้คลาสที่ได้รับของคุณเพื่อให้คุณสามารถพูดว่า "ฉันรู้ว่าซูเปอร์คลาสของฉันช่วยให้คุณสามารถแทนที่สิ่งนี้ได้ แต่ถ้าคุณต้องการได้มาจากฉันคุณจะทำไม่ได้!"
struct Foo
{
virtual void DoStuff();
}
struct Bar : public Foo
{
void DoStuff() final;
}
struct Babar : public Bar
{
void DoStuff(); // error!
}
ตามที่ผู้โพสต์คนอื่นชี้ให้เห็นมันไม่สามารถนำไปใช้กับฟังก์ชั่นที่ไม่ใช่เสมือน
วัตถุประสงค์หนึ่งของคำหลักสุดท้ายคือการป้องกันไม่ให้มีการแทนที่วิธีการโดยไม่ตั้งใจ ในตัวอย่างของฉัน DoStuff () อาจเป็นฟังก์ชันตัวช่วยที่คลาสที่ได้รับนั้นต้องการเปลี่ยนชื่อเพื่อรับพฤติกรรมที่ถูกต้อง ข้อผิดพลาดจะไม่ถูกค้นพบจนกว่าจะทำการทดสอบ
คำหลักสุดท้ายใน C ++ เมื่อเพิ่มเข้าไปในฟังก์ชั่นป้องกันไม่ให้มันถูกแทนที่โดยชั้นฐาน นอกจากนี้เมื่อเพิ่มเข้าไปในชั้นเรียนป้องกันการสืบทอดประเภทใด ๆ ลองพิจารณาตัวอย่างต่อไปนี้ซึ่งแสดงการใช้ตัวระบุสุดท้าย โปรแกรมนี้ล้มเหลวในการรวบรวม
#include <iostream>
using namespace std;
class Base
{
public:
virtual void myfun() final
{
cout << "myfun() in Base";
}
};
class Derived : public Base
{
void myfun()
{
cout << "myfun() in Derived\n";
}
};
int main()
{
Derived d;
Base &b = d;
b.myfun();
return 0;
}
นอกจากนี้:
#include <iostream>
class Base final
{
};
class Derived : public Base
{
};
int main()
{
Derived d;
return 0;
}
เสริมคำตอบของ Mario Knezović:
class IA
{
public:
virtual int getNum() const = 0;
};
class BaseA : public IA
{
public:
inline virtual int getNum() const final {return ...};
};
class ImplA : public BaseA {...};
IA* pa = ...;
...
ImplA* impla = static_cast<ImplA*>(pa);
//the following line should cause compiler to use the inlined function BaseA::getNum(),
//instead of dynamic binding (via vtable or something).
//any class/subclass of BaseA will benefit from it
int n = impla->getNum();
รหัสข้างต้นแสดงให้เห็นถึงทฤษฎี แต่ไม่ได้ทดสอบจริงกับคอมไพเลอร์จริง นิยมมากถ้าใครวางเอาท์พุทถอดชิ้นส่วน
virtual
คำหลักหรือไม่ก็ตาม