Aar*_*her 781 c++ inheritance abstract-class interface pure-virtual
如何设置代表接口的类?这只是一个抽象的基类吗?
Mar*_*som 671
要扩展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
}
};
Run Code Online (Sandbox Code Playgroud)
您不必为虚拟析构函数包含一个主体 - 事实证明,某些编译器在优化空析构函数时遇到问题,您最好使用默认析构函数.
bra*_*ray 236
用纯虚方法创建一个类.通过创建覆盖这些虚拟方法的另一个类来使用该接口.
纯虚方法是一种定义为虚拟并分配给0的类方法.
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
Run Code Online (Sandbox Code Playgroud)
Joe*_*orn 145
除了C#/ Java中的抽象基类之外,你有一个特殊的接口类型类的全部原因是因为C#/ Java不支持多重继承.
C++支持多重继承,因此不需要特殊类型.没有非抽象(纯虚拟)方法的抽象基类在功能上等同于C#/ Java接口.
Dim*_*ima 50
C++本身并没有"接口"的概念.AFAIK,接口首先在Java中引入,以解决缺少多重继承问题.这个概念已经证明是非常有用的,并且通过使用抽象基类可以在C++中实现相同的效果.
抽象基类是一个类,其中至少有一个成员函数(Java lingo中的方法)是使用以下语法声明的纯虚函数:
class A
{
virtual void foo() = 0;
};
Run Code Online (Sandbox Code Playgroud)
无法实例化抽象基类,即您不能声明类A的对象.您只能从A派生类,但任何不提供实现的派生类foo()也将是抽象的.为了不再抽象,派生类必须为它继承的所有纯虚函数提供实现.
请注意,抽象基类不仅仅是一个接口,因为它可以包含非纯虚拟的数据成员和成员函数.接口的等价物是抽象基类,没有任何只有纯虚函数的数据.
而且,正如Mark Ransom指出的那样,抽象基类应该像任何基类一样提供虚拟析构函数.
小智 43
到目前为止我可以测试,添加虚拟析构函数非常重要.我正在使用用它创建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;
}
Run Code Online (Sandbox Code Playgroud)
如果没有运行前面的代码virtual ~IBase() {};,您将看到Tester::~Tester()永远不会调用析构函数.
Rex*_*xar 33
我的答案与其他答案基本相同,但我认为还有两个重要的事情要做:
在接口中声明虚拟析构函数或创建受保护的非虚拟析构函数,以避免在有人尝试删除类型对象时出现未定义的行为IDemo.
使用虚拟继承来避免多重继承问题.(当我们使用接口时,通常会有多重继承.)
和其他答案一样:
通过创建覆盖这些虚拟方法的另一个类来使用该接口.
class IDemo
{
public:
virtual void OverrideMe() = 0;
virtual ~IDemo() {}
}
Run Code Online (Sandbox Code Playgroud)
要么
class IDemo
{
public:
virtual void OverrideMe() = 0;
protected:
~IDemo() {}
}
Run Code Online (Sandbox Code Playgroud)
和
class Child : virtual public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
}
Run Code Online (Sandbox Code Playgroud)gnz*_*lbg 10
在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;
// ...
};
Run Code Online (Sandbox Code Playgroud)
在这种情况下,接口具有引用语义,即您必须确保该对象比接口更长(也可以使接口具有值语义).
这些类型的接口有它们的优点和缺点:
最后,继承是复杂软件设计中所有邪恶的根源.在Sean Parent的Value Semantics和基于概念的多态性中(强烈推荐,此技术的更好版本在那里解释),研究了以下案例:
假设我有一个应用程序,我使用该MyShape接口以多态方式处理我的形状:
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
Run Code Online (Sandbox Code Playgroud)
在您的应用程序中,您使用YourShape界面使用不同的形状执行相同的操作:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Run Code Online (Sandbox Code Playgroud)
现在说你想使用我在你的应用程序中开发的一些形状.从概念上讲,我们的形状具有相同的界面,但为了使我的形状在您的应用程序中工作,您需要扩展我的形状如下:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
Run Code Online (Sandbox Code Playgroud)
首先,根本不可能修改我的形状.此外,多重继承引领了意大利面条代码的道路(想象一下第三个项目就是使用TheirShape界面......如果它们也调用了它们的绘制函数会发生什么my_draw?).
更新:有一些关于基于非继承的多态性的新引用:
上面所有的好答案.您应该记住的另一件事 - 您还可以拥有纯虚拟析构函数.唯一的区别是你仍然需要实现它.
困惑?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
Run Code Online (Sandbox Code Playgroud)
您想要这样做的主要原因是,如果您想提供接口方法,就像我一样,但是将它们覆盖为可选项.
要使类成为接口类需要纯虚方法,但是所有虚方法都有默认实现,因此唯一的生成纯虚方法的方法是析构函数.
在派生类中重新实现析构函数根本没什么大不了的 - 我总是在派生类中重新实现析构函数,无论是否为虚函数.
您还可以考虑使用 NVI(非虚拟接口模式)实现的合约类。例如:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1() = default;
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:
void do_f(Parameters p) override; // From contract 1.
void do_g(Parameters p) override; // From contract 2.
};
Run Code Online (Sandbox Code Playgroud)
如果您使用的是Microsoft的C++编译器,那么您可以执行以下操作:
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法,因为它会导致更小的接口代码,并且生成的代码大小可以显着缩小.使用novtable删除对该类中vtable指针的所有引用,因此您永远不能直接实例化它.请参阅此处的文档 - novtable.
对上面写的内容做一点补充:
首先,确保你的析构函数也是纯虚拟的
其次,您可能希望在实施时虚拟继承(而不是通常),只是为了好的措施。
| 归档时间: |
|
| 查看次数: |
458769 次 |
| 最近记录: |