1

カプセル化 (情報の隠蔽) は非常に有用な概念であり、最小限の詳細のみがクラスの API で公開されるようにします。

しかし、C++ のやり方は少し不十分だと思わずにはいられません。たとえば、次のような (摂氏ベースの) 温度クラスを考えてみましょう。

class tTemp {
    private:
        double temp;
        double tempF (double);
    public:
        tTemp ();
        ~tTemp ();
        setTemp (double);
        double getTemp ();
        double getTempF ();
};

これは非常に単純なケースですが、カプセル化が完全ではないという点を示しています。「本物の」カプセル化は、次のような不要な情報をすべて隠します。

  • tempデータが変数 (およびその型)で内部的に保持されているという事実。
  • 華氏/摂氏変換のための内部ルーチンがあるという事実。

したがって、理想的には、クラスの実装者は上記のヘッダーを使用するように思われますが、クラスのクライアントはパブリック ビットのみを参照します。

誤解しないでほしいのですが、C++ はクライアントがプライベート ビットを使用できないようにするという目的を満たしているため、C++ を批判しているわけではありません。プライベート データと関数。

C++ では、実装者がこの情報を非表示にできるようにするにはどうすればよいですか(可能な場合)? C では、単純に不透明型を使用して内部の詳細を非表示にしますが、C++ ではどのようにそれを行うのでしょうか?

クライアントから完全に隠され、自分のコードだけが知っているのクラスを維持し、そのインスタンスをvoid *可視クラスに保持することができると思います (コード内でキャストします) が、それはかなり面倒なプロセスのようです。C++ で同じ目的を達成するためのより簡単な方法はありますか?

4

4 に答える 4

8

C++ では、「pimpl」(プライベート実装/実装へのポインター) と呼ばれるイディオムを使用して、実装の詳細を隠します。詳細については、この MSDN の記事を参照してください。

つまり、通常どおりヘッダー ファイルでインターフェイスを公開します。あなたのコードを例として使用しましょう:

tTemp.h

class tTemp {
    private:
        class ttemp_impl; // forward declare the implementation class
        std::unique_ptr<ttemp_impl> pimpl;
    public:
        tTemp ();
       ~tTemp ();
       setTemp (double);
       double getTemp (void);
       double getTempF (void);
};

パブリック インターフェイスは残りますが、プライベートな内部はプライベート実装クラスへのスマート ポインターに置き換えられています。この実装クラスは、ヘッダーの対応する .cpp ファイルにのみ配置されており、公開されていません。

tTemp.cpp

class tTemp::ttemp_impl
{
    // put your implementation details here
}

// use the pimpl as necessary from the public interface
// be sure to initialize the pimpl!
tTtemp::tTemp() : pimpl(new ttemp_impl) {}

これには、ヘッダーを変更せずにクラスの内部を変更できるという追加の利点もあります。つまり、クラスのユーザーの再コンパイルが少なくなります。


paxdiablo の pre-C++11 の回答に示されている完全なソリューションについては、unique_ptr代わりにvoid *を使用して、次を使用できます。最初ttemp.h

#include <memory>
class tTemp {
public:
    tTemp();
    ~tTemp();
    void setTemp(double);
    double getTemp (void);
    double getTempF (void);

private:
    class impl;
    std::unique_ptr<impl> pimpl;
};

次に、「隠された」実装ttemp.cpp:

#include "ttemp.h"

struct tTemp::impl {
    double temp;
    impl() { temp = 0; };
    double tempF (void) { return temp * 9 / 5 + 32; };
};

tTemp::tTemp() : pimpl (new tTemp::impl()) {};

tTemp::~tTemp() {}

void tTemp::setTemp (double t) { pimpl->temp = t; }

double tTemp::getTemp (void) { return pimpl->temp; }

double tTemp::getTempF (void) { return pimpl->tempF(); }

そして、最後にttemp_test.cpp:

#include <iostream>
#include <cstdlib>
#include "ttemp.h"

int main (void) {
    tTemp t;
    std::cout << t.getTemp() << "C is " << t.getTempF() << "F\n";
    return 0;
}

そして、paxdiablo のソリューションと同様に、出力は次のようになります。

0C is 32F

より多くの型安全性という追加の利点があります。この回答は C++11 の理想的なソリューションです。コンパイラが C++11 より前の場合は、paxdiablo の回答を参照してください。

于 2013-07-25T02:05:59.467 に答える
4

Don Wakefield がコメントで言及している「インターフェイス クラス / ファクトリ」テクニックを具体化すると思いました。まず、インターフェイスからすべての実装の詳細を抽象化し、へのインターフェイスのみを含む抽象クラスを定義しますTemp

// in interface.h:
class Temp {
    public:
        virtual ~Temp() {}
        virtual void setTemp(double) = 0;
        virtual double getTemp() const = 0;
        virtual double getTempF() const = 0;

        static std::unique_ptr<Temp> factory();
};

オブジェクトを必要とするクライアントはTemp、ファクトリを呼び出してオブジェクトを構築します。ファクトリは、さまざまな条件でインターフェイスのさまざまな実装を返す複雑なインフラストラクチャを提供することも、この例の「Temp をください」ファクトリのように単純なものを提供することもできます。

実装クラスは、すべての純粋仮想関数宣言にオーバーライドを提供することで、インターフェイスを実装できます。

// in implementation.cpp:
class ConcreteTemp : public Temp {
    private:
        double temp;
        static double tempF(double t) { return t * (9.0 / 5) + 32; }
    public:
        ConcreteTemp() : temp() {}
        void setTemp(double t) { temp = t; }
        double getTemp() const { return temp; }
        double getTempF() const { return tempF(temp); }
};

そして、どこか (おそらく同じimplementation.cpp) で、ファクトリを定義する必要があります。

std::unique_ptr<Temp> Temp::factory() {
    return std::unique_ptr<Temp>(new ConcreteTemp);
}

このアプローチは、pimpl よりも少し簡単に拡張できTempます。「秘密の」実装が 1 つしかないのではなく、インターフェースを実装したい人なら誰でも実装できます。仮想ディスパッチ用の言語の組み込みメカニズムを使用してインターフェイス関数呼び出しを実装にディスパッチするため、ボイラープレートも少し少なくなります。

于 2013-07-25T05:19:46.550 に答える
0

プライベート実装 (PIMPL) は、C++ がこの機能を提供できる方法です。unique_ptrバリエーションを CygWin g++ 4.3.4 でコンパイルするのに苦労したのでvoid *、次のように可視クラス内で a を使用する別の方法があります。これにより、C++11 より前のコンパイラや、C++11 の実験的サポートのみを行っていた前述の gcc のようなコンパイラを使用できるようになります。

ttemp.hまず、クライアントに含まれるヘッダー ファイルです。これは、内部実装構造を不透明に宣言するため、それらの内部は完全に隠されます。明らかになっている唯一の詳細は、内部クラスと変数の名前であることがわかります。どちらも、内部がどのように機能するかについての情報を明らかにする必要はありません。

struct tTempImpl;
class tTemp {
public:
    tTemp();
    ~tTemp();
    tTemp (const tTemp&);
    void setTemp(double);
    double getTemp (void);
    double getTempF (void);
private:
    tTempImpl *pimpl;
};

ttemp.cpp次に、不透明なものを宣言および定義し、ユーザーに表示される詳細も定義する実装ファイル。ユーザーはこのコードを見ることがないため、実装方法を知りません。

#include "ttemp.h"

struct tTempImpl {
    double temp;
    tTempImpl() { temp = 0; };
    double tempF (void) { return temp * 9 / 5 + 32; };
};

tTemp::tTemp() : pimpl (new tTempImpl()) {
};

tTemp::~tTemp() {
    delete pimpl;
}

tTemp::tTemp (const tTemp& orig) {
    pimpl = new tTempImpl;
    pimpl->temp = orig.pimpl->temp;
}

void tTemp::setTemp (double t) {
    pimpl->temp = t;
}

double tTemp::getTemp (void) {
    return pimpl->temp;
}

double tTemp::getTempF (void) {
    return pimpl->tempF();
}

内部実装の詳細は、可視クラス自体から保護されていないことに注意してください。内部をアクセサーとミューテーターを持つクラスとして定義することもできますが、この場合は密結合する必要があるため、不要のようです。

上記の 1 つの注意点: 非表示の側面を制御するためにポインターを使用しているため、デフォルトの浅いコピー コンストラクターは、同じプライベート メンバーを参照する 2 つの可視オブジェクトを持つことによって問題を引き起こします (デストラクタでの二重削除につながります)。 . したがって、これを防ぐには、(私が持っているように) ディープコピー コピー コンストラクターを提供する必要があります。

最後に、全体がどのようにつながっているかを示すテスト プログラム:

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

int main (void) {
    tTemp t;
    std::cout << t.getTemp() << "C is " << t.getTempF() << "F\n";
    return 0;
}

もちろん、そのコードの出力は次のとおりです。

0C is 32F
于 2013-07-25T04:18:40.477 に答える