20

クラスとそのパブリックメソッドを宣言するが、そのクラスのプライベートメンバーを定義しないC ++ヘッダーファイル(.h)を作成することは可能ですか?ヘッダーファイルでクラスとそのすべてのメンバーを宣言してから、cppファイルでメソッドを個別に定義する必要があるというページがいくつか見つかりました。Win32 DLLで定義されたクラスが必要であり、適切にカプセル化されている必要があるため、質問します。そのクラスの内部実装は、そのメンバーを含めて変更される可能性がありますが、これらの変更は、クラスを使用するコードには影響しません。 。

これがあれば、コンパイラがオブジェクトのサイズを事前に知ることができなくなると思います。ただし、コンパイラがコンストラクタを使用し、オブジェクトが格納されているメモリ内の場所へのポインタを渡すだけで、「sizeof(MyClass)」を実行させない限り、それで問題ありません。

更新: 回答してくれたすべての人に感謝します!単純なイディオムは、私が話していたことを達成するための良い方法のようです。私は似たようなことをするつもりです:

私のWin32DLLファイルには、次のような個別の関数がたくさんあります。

void * __stdcall DogCreate();
int __stdcall DogGetWeight(void * this);
void __stdcall DogSetWeight(void * this, int weight);

これは、MicrosoftがDLLファイルを書き込む一般的な方法であるため、おそらくそれには十分な理由があると思います。

しかし、C ++がクラスに対して持つ優れた構文を利用したいので、これらすべての関数をラップするラッパークラスを作成します。メンバーは1つで、「void*pimpl」になります。このラッパークラスは非常に単純なので、宣言してヘッダーファイルで定義するだけでもかまいません。しかし、このラッパークラスには、私が知る限り、C++コードをきれいに見せること以外の目的はありません。

4

8 に答える 8

13

pimplイディオムを使用します。

于 2009-04-22T19:35:37.707 に答える
7

pimplイディオムはクラスに void* プライベート データ メンバーを追加します。ただし、欠点があります。その主なものは、抽象型でのポリモーフィズムの使用を困難にすることです。場合によっては、抽象基本クラスとその基本クラスのサブクラスが必要になることがあります。ベクトル内のすべての異なる型へのポインターを収集し、それらのメソッドを呼び出します。さらに、pimpl イディオムの目的がクラスの実装の詳細を隠すことである場合、それはほとんど成功しません。ポインター自体が実装の詳細です。おそらく、不透明な実装の詳細。しかし、それにもかかわらず、実装の詳細。

必要に応じて多態的に使用できる基本型を提供しながら、インターフェイスからすべての実装の詳細を削除するために使用できる pimpl イディオムに代わるものがあります。

DLL のヘッダー ファイル (クライアント コードによって #include されるファイル) で、パブリック メソッドと、クラスのインスタンス化方法を指示する概念 (パブリック ファクトリ メソッドとクローン メソッドなど) のみを含む抽象クラスを作成します。

kennel.h

/****************************************************************
 ***
 ***    The declaration of the kennel namespace & its members
 ***    would typically be in a header file.
 ***/

// Provide an abstract interface class which clients will have pointers to.
// Do not permit client code to instantiate this class directly.

namespace kennel
{
    class Animal
    {
    public:
        // factory method
        static Animal* createDog(); // factory method
        static Animal* createCat(); // factory method

        virtual Animal* clone() const = 0;  // creates a duplicate object
        virtual string speak() const = 0;   // says something this animal might say
        virtual unsigned long serialNumber() const = 0; // returns a bit of state data
        virtual string name() const = 0;    // retuyrns this animal's name
        virtual string type() const = 0; // returns the type of animal this is

        virtual ~Animal() {};   // ensures the correct subclass' dtor is called when deleteing an Animal*
    };
};

...Animal は抽象基本クラスであるため、インスタンス化できません。private ctor を宣言する必要はありません。仮想 dtor の存在により、誰かが である場合deleteAnimal*適切なサブクラスの dtor も呼び出されることが保証されます。

基本型のさまざまなサブクラス (犬や猫など) を実装するには、DLL で実装レベルのクラスを宣言します。これらのクラスは、ヘッダー ファイルで宣言した抽象基本クラスから最終的に派生し、ファクトリ メソッドはこれらのサブクラスの 1 つを実際にインスタンス化します。

dll.cpp:

/****************************************************************
 ***
 ***    The code that follows implements the interface
 ***    declared above, and would typically be in a cc
 ***    file.
 ***/   

// Implementation of the Animal abstract interface
// this implementation includes several features 
// found in real code:
//      Each animal type has it's own properties/behavior (speak)
//      Each instance has it's own member data (name)
//      All Animals share some common properties/data (serial number)
//

namespace
{
    // AnimalImpl provides properties & data that are shared by
    // all Animals (serial number, clone)
    class AnimalImpl : public kennel::Animal    
    {
    public:
        unsigned long serialNumber() const;
        string type() const;

    protected:
        AnimalImpl();
        AnimalImpl(const AnimalImpl& rhs);
        virtual ~AnimalImpl();
    private:
        unsigned long serial_;              // each Animal has its own serial number
        static unsigned long lastSerial_;   // this increments every time an AnimalImpl is created
    };

    class Dog : public AnimalImpl
    {
    public:
        kennel::Animal* clone() const { Dog* copy = new Dog(*this); return copy;}
        std::string speak() const { return "Woof!"; }
        std::string name() const { return name_; }

        Dog(const char* name) : name_(name) {};
        virtual ~Dog() { cout << type() << " #" << serialNumber() << " is napping..." << endl; }
    protected:
        Dog(const Dog& rhs) : AnimalImpl(rhs), name_(rhs.name_) {};

    private:
        std::string name_;
    };

    class Cat : public AnimalImpl
    {
    public:
        kennel::Animal* clone() const { Cat* copy = new Cat(*this); return copy;}
        std::string speak() const { return "Meow!"; }
        std::string name() const { return name_; }

        Cat(const char* name) : name_(name) {};
        virtual ~Cat() { cout << type() << " #" << serialNumber() << " escaped!" << endl; }
    protected:
        Cat(const Cat& rhs) : AnimalImpl(rhs), name_(rhs.name_) {};

    private:
        std::string name_;
    };
};

unsigned long AnimalImpl::lastSerial_ = 0;


// Implementation of interface-level functions
//  In this case, just the factory functions.
kennel::Animal* kennel::Animal::createDog()
{
    static const char* name [] = {"Kita", "Duffy", "Fido", "Bowser", "Spot", "Snoopy", "Smkoky"};
    static const size_t numNames = sizeof(name)/sizeof(name[0]);

    size_t ix = rand()/(RAND_MAX/numNames);

    Dog* ret = new Dog(name[ix]);
    return ret;
}

kennel::Animal* kennel::Animal::createCat()
{
    static const char* name [] = {"Murpyhy", "Jasmine", "Spike", "Heathcliff", "Jerry", "Garfield"};
    static const size_t numNames = sizeof(name)/sizeof(name[0]);

    size_t ix = rand()/(RAND_MAX/numNames);

    Cat* ret = new Cat(name[ix]);
    return ret;
}


// Implementation of base implementation class
AnimalImpl::AnimalImpl() 
: serial_(++lastSerial_) 
{
};

AnimalImpl::AnimalImpl(const AnimalImpl& rhs) 
: serial_(rhs.serial_) 
{
};

AnimalImpl::~AnimalImpl() 
{
};

unsigned long AnimalImpl::serialNumber() const 
{ 
    return serial_; 
}

string AnimalImpl::type() const
{
    if( dynamic_cast<const Dog*>(this) )
        return "Dog";
    if( dynamic_cast<const Cat*>(this) )
        return "Cat";
    else
        return "Alien";
}

これで、ヘッダーにインターフェースが定義され、実装の詳細が完全に分離され、クライアント コードからはまったく見えなくなりました。これを使用するには、DLL にリンクするコードからヘッダー ファイルで宣言されたメソッドを呼び出します。サンプルドライバーは次のとおりです。

main.cpp:

std::string dump(const kennel::Animal* animal)
{
    stringstream ss;
    ss << animal->type() << " #" << animal->serialNumber() << " says '" << animal->speak() << "'" << endl;
    return ss.str();
}

template<class T> void del_ptr(T* p)
{
    delete p;
}

int main()
{
    srand((unsigned) time(0));

    // start up a new farm
    typedef vector<kennel::Animal*> Animals;
    Animals farm;

    // add 20 animals to the farm
    for( size_t n = 0; n < 20; ++n )
    {
        bool makeDog = rand()/(RAND_MAX/2) != 0;
        if( makeDog )
            farm.push_back(kennel::Animal::createDog());
        else
            farm.push_back(kennel::Animal::createCat());
    }

    // list all the animals in the farm to the console
    transform(farm.begin(), farm.end(), ostream_iterator<string>(cout, ""), dump);

    // deallocate all the animals in the farm
    for_each( farm.begin(), farm.end(), del_ptr<kennel::Animal>);

    return 0;
}
于 2009-04-22T21:53:49.090 に答える
3

Googleの「にきびイディオム」または「C++の処理」。

于 2009-04-22T19:34:54.587 に答える
3

はい、これは望ましいことです。簡単な方法の1つは、実装クラスをヘッダーで定義されたクラスから派生させることです。

欠点は、コンパイラがクラスの構築方法を認識しないため、クラスのインスタンスを取得するために何らかのファクトリメソッドが必要になることです。スタックにローカルインスタンスを置くことは不可能です。

于 2009-04-22T19:35:35.367 に答える
2

コンパイラがオブジェクトの大きさなどを認識できるように、ヘッダーですべてのメンバーを宣言する必要があります。

ただし、インターフェースを使用してこれを解決できます。

ext.h:

class ExtClass
{
public:
  virtual void func1(int xy) = 0;
  virtual int func2(XYClass &param) = 0;
};

int.h:

class ExtClassImpl : public ExtClass
{
public:
  void func1(int xy);
  int func2(XYClass&param);
};

int.cpp:

  void ExtClassImpl::func1(int xy)
  {
    ...
  }
  int ExtClassImpl::func2(XYClass&param)
  {
    ...
  }
于 2009-04-23T22:19:01.163 に答える
0

クラスとそのパブリック メソッドを宣言するが、そのクラスのプライベート メンバーを宣言しない C++ ヘッダー ファイル (.h) を作成することは可能ですか?

最も近い答えは PIMPL イディオムです。

Herb Sutterの The Fast Pimpl Idiomを参照してください。

IMO Pimpl は、ヘッダー ファイルが何度も変更される開発の初期段階で非常に役立ちます。Pimplには、ヒープ上の内部オブジェクトの割り当て/割り当て解除によるコストがあります。

于 2009-04-22T19:38:03.643 に答える
-1

クラスThe Handle-Body Idiom in C++ を確認してください

于 2009-04-22T19:38:00.120 に答える