使用类作为基类
Use a class as a BaseClass
我有:
class BASE{
public:
virtual void func1() = 0;
};
然后我有一些派生类,例如:
class Derived1 : public BASE{
public:
void func1() {...implementation....}
};
class Derived2 : public BASE{
public:
void func1() {...implementation....}
};
总的来说,我想做一些类似的事情(伪代码(:
if ( cond ){
BASE b := new Derived1
}
else{
BASE b := new Derived2
}
b.func1();
因此,调用的 func1(( 函数是专门用于派生类的函数。
我尝试做:
BASE b = new Derived1();
但是编译器抱怨。
在C++有可能做到这一点吗?如何?
显然你已经习惯了垃圾收集的OO语言,比如Java。要很好地回答这个问题,从这个角度来看可能是件好事。
当你写东西时
Base b = new Derived1();
在 Java 中,会发生以下情况:
- 指向泛型
Base
对象的指针"&b
"(我称之为β
(在堆栈上分配 - 一个新的
Derived1
对象(我称之为d
(被分配到堆上 -
β
设置为指向Derived
对象。
在Java中你很容易摆脱这种情况的原因是有一个垃圾收集器。只要β
在堆栈上并指向d
,这就不会产生任何影响,因为这样 GC 就知道d
仍然可以访问并且可能正在使用中。但是一旦没有指针再引用d
(例如,因为你声明b
的函数离开了范围(,GC就被允许释放d
占用的空间。很容易,但是垃圾收集有几个缺点,这是我们C++
所以在C++,如果你做类似的事情
Base* b = new Derived1();
直接对应于 Java 版本,您有一个问题:当b
离开范围时,不再引用d
,但它仍然位于堆上!您需要自己删除它
delete b;
(请注意,这有很大的优势,您可以准确地确定它在哪个点被删除,而垃圾收集器可能会让它无用地放置很长时间,并且仅在您开始耗尽内存时才擦除它(。但再一次,这还不够:与垃圾收集器不同,您的程序不会自动知道b
指向Derived1
对象而不是Base
对象,因此delete
会删除认为它正在处理Base
。但是很容易解决这个问题:在Base
类中包含虚拟析构函数,例如
class Base{
public:
virtual void func1() = 0;
virtual ~Base() {}
};
现在,手动删除所有内容的需求显然有点危险:如果您忘记这样做,您就会有内存泄漏,即只要您的程序运行,该对象就永远不会从堆中删除。出于这个原因,应该使用智能指针而不是普通的 C 样式指针,这会在离开范围时自动删除它们指向的对象。在 C++11 中,这些在标准库(标头 <memory>
(中定义。你只是做
std::unique_ptr<Base> b(new Derived1());
现在,当b
离开作用域时,Derived1
对象会自动删除。
在所有这些示例中,调用虚拟函数的工作方式相同:
b->func1();
使用指针(或引用(,否则您的对象将只复制到普通的 BASE 实例中:
BASE* b = new Derived1;
b->func1();
// later...
delete b;
另外,不要忘记在这种情况下使您的析构函数成为虚拟的。
由于整个新/删除的事情只是为了让多态性工作有点麻烦,所以现在人们倾向于使用智能指针:
std::shared_ptr<BASE> b=std::make_shared<Derived1>();
b->func1();
有两种方法可以做到这一点:
-
使用基类类型的指针:
Base *b = new Derived1;
-
如果要创建派生的复制构造,请使用基类类型的 const 引用:
Base const& b = Derived;
-
如果要分配在其他地方创建的派生类对象,请使用基类类型的非 const 引用:
Derived d;
Base& b = d;
尼特斯:
-
为了继承,您需要在类定义中指定以下内容:
class Derived1 : public Base {
-
函数需要具有返回值。
class B{ public: virtual void func() = 0; };
您正在尝试分配指向普通对象的指针。
new Derived1()
此表达式返回一个指针。您要么必须BASE
成为指针(BASE *
(,要么只是在堆栈上创建它(最好是Base b;
(。但在这种情况下,您需要指针(或引用(在类之间进行转换。
此外,您不能在 if-else 语句中创建对象,因为当您以后要使用它时,它将超出范围。
您可以执行以下操作:
BASE *b;
if (cond)
b = new Derived1();
else
b = new Derived2();
b->func1();
当然,你必须记得事后delete
你的指针。考虑改用智能指针。
指针(及其陷阱(的使用已经涵盖,所以我将向您展示如何在没有动态内存分配的情况下使用多态性,这可能证明我是一个异端,甚至考虑它。
首先,让我们看看你的原始代码,修补后编译
:void foo(bool const cond) {
Base* b = 0;
if (cond) { b = new Derived1(); }
else { b = new Derived2(); }
b->func1();
delete b; // do not forget to release acquired memory
}
假设您有一个用于Base
的virtual
析构函数,这工作正常。
程序员进化的下一个合乎逻辑的步骤是使用智能指针来避免编写delete
(delete
仅供初学者和专家库编写者使用(:
void foo(bool const cond) {
std::unique_ptr<Base> b;
if (cond) { b.reset(new Derived1()); }
else { b.reset(new Derived2()); }
b->func1();
}
当然,它仍然需要一个virtual
析构函数来Base
。
让我们意识到这个函数做两件事:
- 在给定条件的情况下选择一个类
- 对生成的实例执行一些工作
我们可以分解它,例如通过提取构建工作:
std::unique_ptr<Base> build(bool const cond) {
if (cond) { return { new Derived1() }; }
return { new Derived2() };
}
void foo(bool const cond) {
std::unique_ptr<Base> b = build(cond);
b->func1();
}
这是大多数人所做的。
我声称还有另一种可能性,我们可以隔离实际工作,而不是隔离构建:
void dowork(Base& b) { b.func1(); /* and perhaps other things */ }
void foo(bool const cond) {
std::unique_ptr<Base> b(cond ? new Derived1() : new Derived2());
work(*b);
}
我们实际上可以更进一步:
void foo(bool const cond) {
if (cond) {
Derived1 d;
work(d);
} else {
Derived2 d;
work(d);
}
}
多态性不需要动态内存分配。
你知道最后一个例子有什么好玩的:
- 在没有 C++11 移动语义的情况下,它工作得很好
- 它不需要在
Base
中具有virtual
析构函数
你忘了吗?
class Derived1 : public BASE
{
};
至少我在你的代码中看不到它 - 派生的 BASE 类
如果函数签名相同,也许在这种情况下接口声明会更合适......然后让每个类实现接口,并将变量声明为你声明的接口类型...
- std::具有相同基类的类的变体
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 基类中的函数名称解析
- C++初始化基类
- 如何通过派生类函数更改基类中的向量
- 如何定义一个纯抽象基类
- 如何使用基类指针引用派生类成员
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 如果基类包含双指针成员,则派生类的构造函数
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 为什么此派生对象无法访问基类的后递减方法?
- 公开最直接的基类模板名称
- 当基类是依赖类型时,这是一个缺陷吗
- 如何使基类的运算符对基类的可变参数数可见(请参阅下面的代码)?
- 模板基类中的静态变量
- C++ 继承:将子类传递给需要基类的函数并获取子类行为
- 继承和友元函数,从基类访问受保护的成员