繁体   English   中英

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

[英]C++ abstract base class constructors/destructors - general correctness

我想要一个 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. 我是否在ICommand正确设置了内容,以便MyCommand必须覆盖CallMeCallMe2

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

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

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

请注意,即使是纯虚拟析构函数也必须定义。

一个具体的实现看起来和你的例子完全一样:

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

构造函数有几个选项 一种选择是禁用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() { }
};

我知道这是旧的,但它仍然是我在这个问题上的第一次打击。 这就是我要做的。

接口头文件 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

是的,我知道“pragma once”严格来说不是标准的,但它对我有用。

请注意,这里没有实现任何内容。 没有构造函数:抽象类不能被实例化。 您可以通过工厂获得指向接口的指针。 要使虚函数调用起作用,必须通过指针调用它们。 虚拟析构函数是默认的,因为它不需要做任何特殊的事情,除了对实现进行多态。 工厂是一个免费的功能。 无需尝试使其成为静态成员或类似的东西。 这不是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::Ptr ,是std::unique_ptr<Foo>的别名。 调用站点根本不知道实现,只知道接口。

输出将如预期的那样:

simple foo
fancy foo

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM