如何设置代表接口的类?这只是一个抽象的基类吗?
您还可以考虑使用NVI(非虚拟接口模式)实现的 Contract 类 . 例如:
struct Contract1 : boost::noncopyable { virtual ~Contract1(); void f(Parameters p) { assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure"); // + class invariants. do_f(p); // Check post-conditions + class invariants. } private: virtual void do_f(Parameters p) = 0; }; ... class Concrete : public Contract1, public Contract2 { private: virtual void do_f(Parameters p); // From contract 1. virtual void do_g(Parameters p); // From contract 2. };
对那里写的内容有点补充:
首先,确保您的析构函数也是纯虚拟的
其次,您可能希望在实施时虚拟(而不是正常)继承,只是为了获得良好的衡量标准 .
要通过bradtgmurray扩展答案,您可能希望通过添加虚拟析构函数对接口的纯虚方法列表进行一个例外 . 这允许您将指针所有权传递给另一方,而不会暴露具体的派生类 . 析构函数不具有任何具体成员 . 将函数定义为虚拟和内联可能看起来相互矛盾,但请相信我 - 事实并非如此 .
class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Parent { public: virtual ~Parent(); }; class Child : public Parent, public IDemo { public: virtual void OverrideMe() { //do stuff } };
您不必为虚拟析构函数包含一个主体 - 事实证明,某些编译器在优化空析构函数时遇到问题,而您最好使用默认析构函数 .
到目前为止我可以测试,添加虚拟析构函数非常重要 . 我正在使用 new 创建的对象并使用 delete 进行销毁 .
new
delete
如果未在接口中添加虚拟析构函数,则不会调用继承类的析构函数 .
class IBase { public: virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes virtual void Describe() = 0; // pure virtual method }; class Tester : public IBase { public: Tester(std::string name); virtual ~Tester(); virtual void Describe(); private: std::string privatename; }; Tester::Tester(std::string name) { std::cout << "Tester constructor" << std::endl; this->privatename = name; } Tester::~Tester() { std::cout << "Tester destructor" << std::endl; } void Tester::Describe() { std::cout << "I'm Tester [" << this->privatename << "]" << std::endl; } void descriptor(IBase * obj) { obj->Describe(); } int main(int argc, char** argv) { std::cout << std::endl << "Tester Testing..." << std::endl; Tester * obj1 = new Tester("Declared with Tester"); descriptor(obj1); delete obj1; std::cout << std::endl << "IBase Testing..." << std::endl; IBase * obj2 = new Tester("Declared with IBase"); descriptor(obj2); delete obj2; // this is a bad usage of the object since it is created with "new" but there are no "delete" std::cout << std::endl << "Tester not defined..." << std::endl; descriptor(new Tester("Not defined")); return 0; }
如果在没有 virtual ~IBase() {}; 的情况下运行上一个代码,您将看到从不调用析构函数 Tester::~Tester() .
virtual ~IBase() {};
Tester::~Tester()
我还是C开发的新手 . 我从Visual Studio(VS)开始 .
然而,似乎没有人提到VS _107161中的 __interface . 我是 not 非常肯定这是否是一个声明接口的好方法 . 但它似乎提供了额外的执法(在the documents中提到) . 这样您就不必显式指定 virtual TYPE Method() = 0; ,因为它会自动转换 .
__interface
virtual TYPE Method() = 0;
__interface IMyInterface { HRESULT CommitX(); HRESULT get_X(BSTR* pbstrName); };
但是,我不使用它,因为我担心跨平台编译兼容性,因为它只能在.NET下使用 .
如果有人有任何有趣的事情,请分享 . :-)
谢谢 .
使用纯虚方法创建一个类 . 通过创建覆盖这些虚拟方法的另一个类来使用该接口 .
纯虚方法是一种定义为虚拟并分配给0的类方法 .
class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Child : public IDemo { public: virtual void OverrideMe() { //do stuff } };
除了C#/ Java中的抽象基类之外,你有一个特殊的接口类型类的全部原因是因为C#/ Java不支持多重继承 .
C支持多重继承,因此不需要特殊类型 . 没有非抽象(纯虚拟)方法的抽象基类在功能上等同于C#/ Java接口 .
我的答案与其他答案基本相同,但我认为还有两个重要的事情要做:
在接口中声明虚拟析构函数或创建受保护的非虚拟析构函数,以避免在有人尝试删除 IDemo 类型的对象时出现未定义的行为 .
IDemo
使用虚拟继承来避免多重继承问题 . (当我们使用接口时,通常会有多重继承 . )
和其他答案一样:
使用纯虚方法创建一个类 .
通过创建覆盖这些虚拟方法的另一个类来使用该接口 .
class IDemo { public: virtual void OverrideMe() = 0; virtual ~IDemo() {} }
要么
class IDemo { public: virtual void OverrideMe() = 0; protected: ~IDemo() {} }
和
class Child : virtual public IDemo { public: virtual void OverrideMe() { //do stuff } }
如果您使用的是Microsoft的C编译器,那么您可以执行以下操作:
struct __declspec(novtable) IFoo { virtual void Bar() = 0; }; class Child : public IFoo { public: virtual void Bar() override { /* Do Something */ } }
我喜欢这种方法,因为它会导致更小的接口代码,并且生成的代码大小可以显着缩小 . 使用novtable删除对该类中vtable指针的所有引用,因此您永远不能直接实例化它 . 请参阅此处的文档 - novtable .
以下是c标准中 abstract class 的定义
abstract class
n4687
13.4.2
抽象类是一个只能用作其他类的基类的类;除了作为派生自它的类的子对象之外,不能创建抽象类的任何对象 . 如果一个类至少有一个纯虚函数,则它是抽象的 .
C中没有“接口”本身的概念 . AFAIK,接口首先在Java中引入,以解决缺少多重继承问题 . 这个概念已经证明是非常有用的,并且通过使用抽象基类可以在C中实现相同的效果 .
抽象基类是一个类,其中至少有一个成员函数(Java lingo中的方法)是使用以下语法声明的纯虚函数:
class A { virtual void foo() = 0; };
无法实例化抽象基类,即 . 即您不能声明类A的对象 . 您只能从A派生类,但任何不提供 foo() 实现的派生类也将是抽象的 . 为了不再抽象,派生类必须为它继承的所有纯虚函数提供实现 .
foo()
请注意,抽象基类不仅仅是一个接口,因为它可以包含非纯虚拟的数据成员和成员函数 . 接口的等价物是抽象基类没有任何只有纯虚函数的数据 .
而且,正如Mark Ransom指出的那样,抽象基类应该像任何基类一样提供虚拟析构函数 .
虽然 virtual 确实是定义接口的事实标准,但我们不要忘记经典的类C模式,它带有C语言中的构造函数:
virtual
struct IButton { void (*click)(); // might be std::function(void()) if you prefer IButton( void (*click_)() ) : click(click_) { } }; // call as: // (button.*click)();
这样做的好处是,您可以重新绑定事件运行时而无需再次构造类(因为C没有更改多态类型的语法,这是变色龙类的变通方法) .
提示:
您可以从此继承为基类(允许虚拟和非虚拟)并在后代的构造函数中填充 click .
click
您可能将函数指针作为 protected 成员并具有 public 引用和/或getter .
protected
public
如上所述,这允许您在运行时切换实现 . 因此,这也是管理国家的一种方式 . 根据代码中 if 与状态变化的数量,这可能比 switch() es或 if 更快(预计周转时间为3-4 if ,但始终先测量 .
if
switch()
如果在函数指针上选择 std::function<> ,则可以在 IBase 中管理所有对象数据 . 从这一点开始,您可以获得 IBase 的值示意图(例如, std::vector<IBase> 将起作用) . 请注意,这可能会更慢,具体取决于您的编译器和STL代码;此外,当与函数指针甚至虚函数相比时, std::function<> 的当前实现往往会产生开销(这可能在将来发生变化) .
std::function<>
IBase
std::vector<IBase>
class Shape { public: // pure virtual function providing interface framework. virtual int getArea() = 0; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; class Rectangle: public Shape { public: int getArea() { return (width * height); } }; class Triangle: public Shape { public: int getArea() { return (width * height)/2; } }; int main(void) { Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); cout << "Rectangle area: " << Rect.getArea() << endl; Tri.setWidth(5); Tri.setHeight(7); cout << "Triangle area: " << Tri.getArea() << endl; return 0; }
结果:矩形区域:35三角形区域:17
我们已经看到抽象类如何根据getArea()定义接口,另外两个类实现相同的函数,但使用不同的算法来计算特定于形状的区域 .
在C 11中,您可以轻松地完全避免继承:
struct Interface { explicit Interface(SomeType& other) : foo([=](){ return other.my_foo(); }), bar([=](){ return other.my_bar(); }), /*...*/ {} explicit Interface(SomeOtherType& other) : foo([=](){ return other.some_foo(); }), bar([=](){ return other.some_bar(); }), /*...*/ {} // you can add more types here... // or use a generic constructor: template<class T> explicit Interface(T& other) : foo([=](){ return other.foo(); }), bar([=](){ return other.bar(); }), /*...*/ {} const std::function<void(std::string)> foo; const std::function<void(std::string)> bar; // ... };
在这种情况下,接口具有引用语义,即您必须确保对象比接口更长(也可以使接口具有值语义) .
这些类型的接口有其优点和缺点:
他们require more memory比基于继承的多态性 .
他们are in general faster比基于继承的多态性 .
在您知道最终类型的情况下,they are much faster!(某些编译器,如gcc和clang在没有/继承具有虚函数的类型的类型中执行更多优化) .
最后,继承是复杂软件设计中所有邪恶的根源 . 在Sean Parent's Value Semantics and Concepts-based Polymorphism(强烈推荐,这里解释了这种技术的更好版本),研究了以下案例:
假设我有一个应用程序,我使用 MyShape 接口以多态方式处理我的形状:
MyShape
struct MyShape { virtual void my_draw() = 0; }; struct Circle : MyShape { void my_draw() { /* ... */ } }; // more shapes: e.g. triangle
在您的应用程序中,使用 YourShape 界面对不同形状执行相同操作:
YourShape
struct YourShape { virtual void your_draw() = 0; }; struct Square : YourShape { void your_draw() { /* ... */ } }; /// some more shapes here...
现在说你想使用我在你的应用程序中开发的一些形状 . 从概念上讲,我们的形状具有相同的界面,但为了使我的形状在您的应用程序中工作,您需要扩展我的形状如下:
struct Circle : MyShape, YourShape { void my_draw() { /*stays the same*/ }; void your_draw() { my_draw(); } };
首先,根本不可能修改我的形状 . 此外,多重继承引领了意大利面条代码的道路(想象第三个项目即使用 TheirShape 接口......如果它们也调用它们的绘制函数 my_draw 会发生什么?) .
TheirShape
my_draw
更新:有一些关于基于非继承的多态性的新引用:
肖恩父母的Inheritance is the base class of evil谈话 .
Sean Parent的Value-semantics and concept-based polymorphism谈话 .
Pyry Jahkola的Inheritance free polymorphism谈话和poly library docs .
Zach Laine的Pragmatic Type Erasure: Solving OOP Problems with an Elegant Design Pattern谈话 .
Andrzej的C博客 - 类型擦除部分i,ii,iii和iv .
Runtime Polymorphic Generic Programming—Mixing Objects and Concepts in ConceptC++
Boost.TypeErasure docs
Adobe Poly docs
Boost.Any,std::any proposal (revision 3),Boost.Spirit::hold_any .
上面所有的好答案 . 还应该记住一件额外的事情 - 你也可以拥有一个纯粹的虚拟析构函数 . 唯一的区别是你仍然需要实现它 .
困惑?
--- header file ---- class foo { public: foo() {;} virtual ~foo() = 0; virtual bool overrideMe() {return false;} }; ---- source ---- foo::~foo() { }
您想要这样做的主要原因是,如果您想提供接口方法,就像我一样,但要将它们覆盖为可选项 .
要使类成为接口类,需要一个纯虚方法,但所有虚方法都有默认实现,因此生成纯虚拟的唯一方法是析构函数 .
在派生类中重新实现析构函数根本没什么大不了的 - 我总是在派生类中重新实现析构函数,无论是否为虚函数 .
15 回答
您还可以考虑使用NVI(非虚拟接口模式)实现的 Contract 类 . 例如:
对那里写的内容有点补充:
首先,确保您的析构函数也是纯虚拟的
其次,您可能希望在实施时虚拟(而不是正常)继承,只是为了获得良好的衡量标准 .
要通过bradtgmurray扩展答案,您可能希望通过添加虚拟析构函数对接口的纯虚方法列表进行一个例外 . 这允许您将指针所有权传递给另一方,而不会暴露具体的派生类 . 析构函数不具有任何具体成员 . 将函数定义为虚拟和内联可能看起来相互矛盾,但请相信我 - 事实并非如此 .
您不必为虚拟析构函数包含一个主体 - 事实证明,某些编译器在优化空析构函数时遇到问题,而您最好使用默认析构函数 .
到目前为止我可以测试,添加虚拟析构函数非常重要 . 我正在使用
new
创建的对象并使用delete
进行销毁 .如果未在接口中添加虚拟析构函数,则不会调用继承类的析构函数 .
如果在没有
virtual ~IBase() {};
的情况下运行上一个代码,您将看到从不调用析构函数Tester::~Tester()
.我还是C开发的新手 . 我从Visual Studio(VS)开始 .
然而,似乎没有人提到VS _107161中的
__interface
. 我是 not 非常肯定这是否是一个声明接口的好方法 . 但它似乎提供了额外的执法(在the documents中提到) . 这样您就不必显式指定virtual TYPE Method() = 0;
,因为它会自动转换 .如果有人有任何有趣的事情,请分享 . :-)
谢谢 .
使用纯虚方法创建一个类 . 通过创建覆盖这些虚拟方法的另一个类来使用该接口 .
纯虚方法是一种定义为虚拟并分配给0的类方法 .
除了C#/ Java中的抽象基类之外,你有一个特殊的接口类型类的全部原因是因为C#/ Java不支持多重继承 .
C支持多重继承,因此不需要特殊类型 . 没有非抽象(纯虚拟)方法的抽象基类在功能上等同于C#/ Java接口 .
我的答案与其他答案基本相同,但我认为还有两个重要的事情要做:
在接口中声明虚拟析构函数或创建受保护的非虚拟析构函数,以避免在有人尝试删除
IDemo
类型的对象时出现未定义的行为 .使用虚拟继承来避免多重继承问题 . (当我们使用接口时,通常会有多重继承 . )
和其他答案一样:
使用纯虚方法创建一个类 .
通过创建覆盖这些虚拟方法的另一个类来使用该接口 .
要么
和
如果您使用的是Microsoft的C编译器,那么您可以执行以下操作:
我喜欢这种方法,因为它会导致更小的接口代码,并且生成的代码大小可以显着缩小 . 使用novtable删除对该类中vtable指针的所有引用,因此您永远不能直接实例化它 . 请参阅此处的文档 - novtable .
以下是c标准中
abstract class
的定义n4687
13.4.2
C中没有“接口”本身的概念 . AFAIK,接口首先在Java中引入,以解决缺少多重继承问题 . 这个概念已经证明是非常有用的,并且通过使用抽象基类可以在C中实现相同的效果 .
抽象基类是一个类,其中至少有一个成员函数(Java lingo中的方法)是使用以下语法声明的纯虚函数:
无法实例化抽象基类,即 . 即您不能声明类A的对象 . 您只能从A派生类,但任何不提供
foo()
实现的派生类也将是抽象的 . 为了不再抽象,派生类必须为它继承的所有纯虚函数提供实现 .请注意,抽象基类不仅仅是一个接口,因为它可以包含非纯虚拟的数据成员和成员函数 . 接口的等价物是抽象基类没有任何只有纯虚函数的数据 .
而且,正如Mark Ransom指出的那样,抽象基类应该像任何基类一样提供虚拟析构函数 .
虽然
virtual
确实是定义接口的事实标准,但我们不要忘记经典的类C模式,它带有C语言中的构造函数:这样做的好处是,您可以重新绑定事件运行时而无需再次构造类(因为C没有更改多态类型的语法,这是变色龙类的变通方法) .
提示:
您可以从此继承为基类(允许虚拟和非虚拟)并在后代的构造函数中填充
click
.您可能将函数指针作为
protected
成员并具有public
引用和/或getter .如上所述,这允许您在运行时切换实现 . 因此,这也是管理国家的一种方式 . 根据代码中
if
与状态变化的数量,这可能比switch()
es或if
更快(预计周转时间为3-4if
,但始终先测量 .如果在函数指针上选择
std::function<>
,则可以在IBase
中管理所有对象数据 . 从这一点开始,您可以获得IBase
的值示意图(例如,std::vector<IBase>
将起作用) . 请注意,这可能会更慢,具体取决于您的编译器和STL代码;此外,当与函数指针甚至虚函数相比时,std::function<>
的当前实现往往会产生开销(这可能在将来发生变化) .结果:矩形区域:35三角形区域:17
我们已经看到抽象类如何根据getArea()定义接口,另外两个类实现相同的函数,但使用不同的算法来计算特定于形状的区域 .
在C 11中,您可以轻松地完全避免继承:
在这种情况下,接口具有引用语义,即您必须确保对象比接口更长(也可以使接口具有值语义) .
这些类型的接口有其优点和缺点:
他们require more memory比基于继承的多态性 .
他们are in general faster比基于继承的多态性 .
在您知道最终类型的情况下,they are much faster!(某些编译器,如gcc和clang在没有/继承具有虚函数的类型的类型中执行更多优化) .
最后,继承是复杂软件设计中所有邪恶的根源 . 在Sean Parent's Value Semantics and Concepts-based Polymorphism(强烈推荐,这里解释了这种技术的更好版本),研究了以下案例:
假设我有一个应用程序,我使用
MyShape
接口以多态方式处理我的形状:在您的应用程序中,使用
YourShape
界面对不同形状执行相同操作:现在说你想使用我在你的应用程序中开发的一些形状 . 从概念上讲,我们的形状具有相同的界面,但为了使我的形状在您的应用程序中工作,您需要扩展我的形状如下:
首先,根本不可能修改我的形状 . 此外,多重继承引领了意大利面条代码的道路(想象第三个项目即使用
TheirShape
接口......如果它们也调用它们的绘制函数my_draw
会发生什么?) .更新:有一些关于基于非继承的多态性的新引用:
肖恩父母的Inheritance is the base class of evil谈话 .
Sean Parent的Value-semantics and concept-based polymorphism谈话 .
Pyry Jahkola的Inheritance free polymorphism谈话和poly library docs .
Zach Laine的Pragmatic Type Erasure: Solving OOP Problems with an Elegant Design Pattern谈话 .
Andrzej的C博客 - 类型擦除部分i,ii,iii和iv .
Runtime Polymorphic Generic Programming—Mixing Objects and Concepts in ConceptC++
Boost.TypeErasure docs
Adobe Poly docs
Boost.Any,std::any proposal (revision 3),Boost.Spirit::hold_any .
上面所有的好答案 . 还应该记住一件额外的事情 - 你也可以拥有一个纯粹的虚拟析构函数 . 唯一的区别是你仍然需要实现它 .
困惑?
您想要这样做的主要原因是,如果您想提供接口方法,就像我一样,但要将它们覆盖为可选项 .
要使类成为接口类,需要一个纯虚方法,但所有虚方法都有默认实现,因此生成纯虚拟的唯一方法是析构函数 .
在派生类中重新实现析构函数根本没什么大不了的 - 我总是在派生类中重新实现析构函数,无论是否为虚函数 .