web-dev-qa-db-ja.com

C ++抽象基本クラスコンストラクター/デストラクター-一般的な正確性

最近、私は開発者として馬鹿げているので、思い切って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. ICommandMyCommandおよびCallMe2をオーバーライドする必要があるように、CallMeで正しく設定しましたか。

本当にシンプルなものを見逃していないことを願っています...

16
Stuart Blackler

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 がいくつかあります。 1つのオプションは、ICommandのデフォルトコンストラクターを無効にすることです。これにより、サブクラスはhaveを行い、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() { }
};
24
e.James

これは古いのはわかっていますが、それでもこの問題に対する最初のヒットです。これが私のやり方です。

インターフェイスヘッダー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::Ptrのエイリアスであるstd::unique_ptr<Foo>型です。コールサイトは、インターフェースについてのみ、実装についての知識はありません。

出力は期待どおりになります。

simple foo
fancy foo
0
Theo