1

我有:

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++ 中做到这一点?如何?

4

7 回答 7

3

使用指针(或引用),否则您的对象将被复制到普通的 BASE 实例中:

BASE* b = new Derived1;
b->func1();

// later...
delete b;

另外,不要忘记在这种情况下使您的析构函数成为虚拟的。

由于整个 new/delete 只是为了让多态起作用有点麻烦,现在人们倾向于使用智能指针:

std::shared_ptr<BASE> b=std::make_shared<Derived1>();
b->func1();
于 2012-05-06T12:30:23.407 回答
3

显然您已经习惯了垃圾收集的面向对象语言,例如 Java。要很好地回答这个问题,从这个角度来看可能会很好。

当你写东西时

Base b = new Derived1();

在 Java 中,会发生以下情况:

  1. 指向通用对象的指针“ &b”(我称之为)在堆栈上分配βBase
  2. 在堆上分配了一个新Derived1对象(我称之为)d
  3. β设置为指向Derived对象。

在 Java 中你很容易摆脱这种情况的原因是有一个垃圾收集器。只要β在堆栈上并指向,这将不会产生任何影响d,因为这样 GC 知道d仍然可以访问并且可能正在使用中。但是一旦不再有指针引用d(例如,因为您声明的函数b离开了作用域),GC 就被允许释放d. 很简单,但是垃圾收集有几个缺点,这是我们在 C++ 中不想要的。

所以在 C++ 中,如果你做类似的事情

Base* b = new Derived1();

直接对应Java版本,你有一个问题:b离开范围时,没有任何引用d,但它仍然在堆上!。你需要自己删除

delete b;

(请注意,这有一个很大的优势,您可以准确地确定它在什么时候被删除,而垃圾收集器可能会让它无用地闲置很长时间,并且只有在您开始耗尽内存时才将其擦除)。但是再一次,这还不够:与垃圾收集器不同,您的程序不会自动知道b指向Derived1对象而不是Base对象,因此deletedelete 会认为它正在处理一个Base. 但是很容易理解这一点:在Base类中包含一个虚拟析构函数,比如

class Base{
 public:
  virtual void func1() = 0;
  virtual ~Base() {}  
};


现在,这种手动删除所有内容的需要显然有点危险:如果您忘记这样做,就会发生内存泄漏,即只要您的程序运行,对象就永远不会从堆中删除。出于这个原因,应该使用智能指针而不是普通的 C 样式指针,这会在离开作用域时自动删除它们指向的对象。在 C++11 中,这些是在标准库 (header <memory>) 中定义的。你只是做

std::unique_ptr<Base> b(new Derived1());

现在,当b离开作用域时,Derived1对象会被自动删除。

在所有这些示例中,调用虚函数的工作方式相同:

b->func1();
于 2012-05-06T13:23:51.393 回答
2

有两种方法可以做到这一点:

  • 使用基类类型的指针:

    Base *b = new Derived1;

  • 如果要在复制构造上创建派生,请使用类型基类的 const 引用:

    Base const& b = Derived;

  • 如果要分配在其他地方创建的派生类对象,请使用基类类型的非常量引用:

    Derived d; Base& b = d;

尼特:

  • 为了继承,您需要在类定义中指定:

    class Derived1 : public Base {

  • 你的函数需要有一个返回值。

    class B{ public: virtual void func() = 0; };

于 2012-05-06T12:31:52.623 回答
1

您正在尝试将指针分配给普通对象。

new Derived1()

此表达式返回一个指针。您要么必须创建BASE一个指针 ( BASE *),要么只是在堆栈上创建它(最好,Base b;)。但是,在这种情况下,您需要指针(或引用)来在类之间进行转换。

此外,您不能在 if-else 语句中创建对象,因为它会超出您以后想要使用它的范围。

这是您可以做的事情:

BASE *b;

if (cond)
    b = new Derived1();
else
    b = new Derived2();

b->func1();

当然,delete之后你必须记住你的指针。考虑简单地使用智能指针。

于 2012-05-06T12:27:59.417 回答
1

已经涵盖了指针(及其陷阱)的使用,所以我将向您展示如何在没有动态内存分配的情况下使用多态性,这可能证明我是一个即使考虑它的异端。

首先,让我们看看你的原始代码,修补后编译:

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
}

假设你有一个virtual析构函数,它工作正常Base

程序员进化的下一个合乎逻辑的步骤是使用智能指针来避免编写deletedelete仅由初学者和专家库编写者使用):

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 移动语义,它工作得很好
  • 它不需要有virtual析构函数Base
于 2012-05-06T13:54:10.820 回答
0

你忘了这个吗?

class Derived1 : public BASE
{
};

至少我在您的代码中没有看到它 - 派生的 BASE 类

于 2012-05-06T12:29:39.070 回答
0

如果函数签名相同,那么在这种情况下,接口声明可能更合适......然后让每个类实现接口,并将变量声明为您声明的接口类型......

于 2012-05-06T12:31:10.707 回答