C ++抽象基类构造函数/析构函数 - 一般正确性

时间:2011-12-14 23:53:36

标签: c++ inheritance abstract-class virtual-inheritance

最近我作为开发人员愚蠢,所以我冒险尝试了一本C ++书并学习如何正确地做事。在我脑海里,我知道自己想做什么。我实际上想要一个Interface,当继承时,必须被覆盖(如果可能的话?)。到目前为止,我有以下内容:

class ICommand{

public:
    //  Virtual constructor. Needs to take a name as parameter
    //virtual ICommand(char*) =0;
    //  Virtual destructor, prevents memory leaks by forcing clean up on derived classes?
    //virtual ~ICommand() =0; 
    virtual void CallMe() =0;
    virtual void CallMe2() =0;
};

class MyCommand : public ICommand
{
public:
    // Is this correct?
    MyCommand(char* Name) { /* do stuff */ }
    virtual void CallMe() {}
    virtual void CallMe2() {}
};

我故意离开了我认为构造函数/析构函数应该在ICommand中实现的方式。我知道如果删除注释,它将无法编译。请某人:

  1. 告诉我如何在ICommand中声明构造函数/析构函数以及如何在MyCommand
  2. 中使用它们
  3. 我是否已在ICommand中正确设置,以便MyCommand必须覆盖CallMeCallMe2
  4. 我希望我没有错过任何非常简单的事情......

2 个答案:

答案 0 :(得分:23)

C ++不允许使用虚拟构造函数。一个简单的实现(没有虚构造函数)看起来像这样:

class ICommand {
public:
    virtual ~ICommand() = 0;
    virtual void callMe() = 0;
    virtual void callMe2() = 0;
};

ICommand::~ICommand() { } // all destructors must exist

请注意,即使是纯虚拟析构函数must也是如此。

具体实现看起来与您的示例完全相同:

class MyCommand : public ICommand {
public:
    virtual void callMe() { }
    virtual void callMe2() { }
};

构造函数有几个options。一个选项是禁用ICommand的默认构造函数,以便子类具有来实现调用ICommand构造函数的构造函数:

#include <string>

class ICommand {
private:
    const std::string name;
    ICommand();
public:
    ICommand(const std::string& name) : name(name) { }
    virtual ~ICommand() = 0;
    virtual void callMe() = 0;
    virtual void callMe2() = 0;
};

ICommand::~ICommand() { } // all destructors must exist

具体实现现在看起来像这样:

class MyCommand : public ICommand {
public:
    MyCommand(const std::string& name) : ICommand(name) { }
    virtual void callMe() { }
    virtual void callMe2() { }
};

答案 1 :(得分:0)

我知道这是个老字号,但这仍然是我在此问题上的第一篇。这就是我要做的。

接口头foo.h:

#pragma once
#include <memory>

enum class Implementations {Simple, Fancy};

class Foo
{
public:
    using Ptr = std::unique_ptr<Foo>;
    virtual ~Foo() = default;
    virtual void do_it() = 0;
};

Foo::Ptr create_foo(Implementations impl); // factory

是的,我知道严格意义上讲“一次编译指示”不是标准的,但是对我有用。

请注意,此处未执行任何操作。没有构造函数:不能实例化抽象类。您将在工厂获得指向该接口的指针。为了使虚函数调用起作用,必须通过指针对其进行调用。默认是虚拟析构函数,因为它除了对实现进行多态化外不必执行任何特殊操作。工厂是免费功能。无需尝试使其成为静态成员或类似的东西。这不是java。

接口foo.cpp:

#include "foo.h"
#include "foo_impl.h"

Foo::Ptr create_foo(Implementations impl)
{
    switch (impl)
    {
    case Implementations::Simple:
        return std::make_unique<Simple_foo>();
    case Implementations::Fancy:
        return std::make_unique<Fancy_foo>();
    default:
        return nullptr;
    }
}

在这里实施工厂。请注意,工厂必须知道实现。这就是为什么我们不以内联方式实现它:如果是内联,则接口头必须包含实现头,并且通过它,实现的知识将“泄漏”到呼叫站点。

实现标头foo_impl.h:

#pragma once
#include "foo.h"

class Simple_foo : public Foo
{
    void do_it() override;
};

class Fancy_foo : public Foo
{
    void do_it() override;
};

没什么特别的,只需重写接口的虚拟功能即可。因为这个例子很简单,所以我将两个实现都放在了相同的文件中。在实际应用中会有所不同。

实现foo_impl.cpp:

#include "foo_impl.h"
#include <iostream>

void Simple_foo::do_it()
{
    std::cout << "simple foo\n";
}

void Fancy_foo::do_it()
{
    std::cout << "fancy foo\n";
}

只需实现这些功能。

main.cpp:

#include "foo.h"

int main()
{
    auto sf = create_foo(Implementations::Simple);
    sf->do_it();
    auto ff = create_foo(Implementations::Fancy);
    ff->do_it();
    return 0;
}

通过枚举,我们可以选择所需的实现。指针的类型为Foo::Ptrstd::unique_ptr<Foo>的别名。呼叫站点完全不了解实现,只知道接口。

输出将符合预期:

simple foo
fancy foo