6

次のような一般的なコードがあるとしましょう。

y.hpp:

#ifndef Y_HPP
#define Y_HPP

// LOTS OF FILES INCLUDED

template <class T>
class Y 
{
public:
  T z;
  // LOTS OF STUFF HERE
};

#endif

ここで、作成したクラス (たとえば X) で Y を使用できるようにしたいと考えています。ただし、X のユーザーが Y ヘッダーを含める必要はありません。

そこで、クラス X を次のように定義します。

x.hpp:

#ifndef X_HPP
#define X_HPP

template <class T>
class Y;

class X
{
public:
  ~X();
  void some_method(int blah);
private:
  Y<int>* y_;
};

#endif

y_ はポインターであるため、その実装を含める必要がないことに注意してください。

実装は、個別にコンパイルされた x.cpp にあります。

x.cpp:

#include "x.hpp"
#include "y.hpp"

X::~X() { delete y_; }
void X::someMethod(int blah) { y_->z = blah; }

したがって、クライアントは「y.hpp」ヘッダーをすべて含めて処理することなく、「x.hpp」を含めるだけで X を使用できます。

main.cpp:

#include "x.hpp"

int main() 
{
  X x;
  x.blah(42);
  return 0; 
}

main.cppこれで、 とをx.cpp別々にコンパイルできるようになり、コンパイル時main.cppに を含める必要がなくなりましたy.hpp

ただし、このコードでは生のポインターを使用する必要があり、さらに削除を使用する必要がありました。

だからここに私の質問があります:

(1) Y ヘッダーを含める必要なく、Y を X の直接メンバー (Y へのポインターではない) にする方法はありますか? (この質問に対する答えはノーだと強く思います)

(2)スマート ポインター クラスを使用して、割り当てられたヒープ Y を処理する方法はありますか? unique_ptr当然の選択のように思えますが、行を変更するとx.hpp

から:

Y<int>* y_; 

に:

std::unique_ptr< Y<int> > y_;

を含め、c++0x モードでコンパイルすると、次のエラーが表示されます。

/usr/include/c++/4.4/bits/unique_ptr.h:64: error: invalid application of ‘sizeof’ to incomplete type ‘Y<int>’ 
/usr/include/c++/4.4/bits/unique_ptr.h:62: error: static assertion failed: "can't delete pointer to incomplete type"

とにかく、生のポインタの代わりに標準のスマートポインタを使用し、カスタムデストラクタで生の削除を行うことでこれを行うことはありますか?

解決:

ハワード・ヒナントはそれを正しく理解しています。私たちがする必要があるのは、次のように変更することだけx.hppですx.cpp

x.hpp:

#ifndef X_HPP
#define X_HPP

#include <memory>

template <class T>
class Y;

class X
{
public:
  X(); // ADD CONSTRUCTOR FOR X();
  ~X();
  void some_method(int blah);
private:
  std::unique_ptr< Y<int> > y_;
};

#endif

x.cpp:

#include "x.hpp"
#include "y.hpp"

X::X() : y_(new Y<int>()) {} // ADD CONSTRUCTOR FOR X();
X::~X() {}
void X::someMethod(int blah) { y_->z = blah; }

そして、unique_ptr を使用することをお勧めします。ありがとうハワード!

解決策の根拠:

私が間違っている場合は修正できますが、このコードの問題は、暗黙のデフォルトコンストラクターが Y をデフォルトで初期化しようとしていて、Y について何も知らないため、それができないことです。コンストラクターを別の場所で定義すると明示的に言うことで、コンパイラーは「Y は別の場所でコンパイルされているので、Y の作成について心配する必要はありません」と考えます。

本当に、最初にコンストラクターを追加する必要がありました。コンストラクターがないと、プログラムにバグが発生します。

4

3 に答える 3

12

unique_ptrまたはを使用shared_ptrして、不完全なタイプを処理できます。を使用する場合は、これまでと同じようにshared_ptrアウトライン~X()を作成する必要があります。を使用する場合は、と(または構築に使用しているコンストラクター)unique_ptrの両方の概要を説明する必要があります。完全な型を要求しているのは、暗黙的に生成されたデフォルトのコンストラクターです。~X()X()XXY<int>

shared_ptrとは両方ともunique_ptr、不完全なタイプで誤ってdeleteを呼び出すことからユーザーを保護しています。そのため、そのような保護を提供しない生のポインターよりも優れています。その理由unique_ptrは、X()動的削除機能ではなく静的削除機能を備えているという事実に要約する必要があります。

編集:より深い説明

との静的削除機能と動的削除機能の違いによりunique_ptrshared_ptr2つのスマートポインタはelement_type異なる場所で完全である必要があります。

unique_ptr<A>次の場合にAを完了する必要があります。

  • ~unique_ptr<A>();

しかし、そうではありません:

  • unique_ptr<A>();
  • unique_ptr<A>(A*);

shared_ptr<A>次の場合にAを完了する必要があります。

  • shared_ptr<A>(A*);

しかし、そうではありません:

  • shared_ptr<A>();
  • ~shared_ptr<A>();

そして最後に、暗黙的に生成されたctorは、スマートポインターのデフォルトctorスマートポインターdtorのX()両方を呼び出します(例外がスローされた場合-例外がスローされた場合でも、そうでないことがわかっていても)。X()

結論:完全である必要がXあるスマートポインターメンバーを呼び出すそのメンバーは、完全であるソースにアウトライン化する必要があります。 element_typeelement_type

そして、についてのクールなことは、アウトライン化する必要があるものについて間違っunique_ptrshared_ptr推測した場合、または完全なものを必要とする特別なメンバーが暗黙的に生成されていることに気付いていない場合element_type、これらのスマートポインターは(時には不十分な言葉で)を教えてくれることです)コンパイル時エラー。

于 2011-03-28T14:45:46.200 に答える
7

1) そうです、答えは「いいえ」です。コンパイラはメンバーオブジェクトのサイズを知っている必要があり、Y 型の定義がなければそれを知ることはできません。

2) boost::shared_ptr(またはtr1::shared_ptr) は完全な型のオブジェクトを必要としません。したがって、それによって暗示されるオーバーヘッドを許容できる場合は、次のように役立ちます。

クラス テンプレートは、ポイントされるオブジェクトの型である T でパラメーター化されます。shared_ptr とそのメンバー関数のほとんどは、T に要件を課しません。不完全型または void を指定できます。

編集unique_ptr:ドキュメントをチェックしました。代わりにそれを使用できるようです:が構築~X()されている場所で が定義されていることを確認してください。unique_ptr<>

于 2011-03-28T14:17:38.347 に答える
0

pimplイディオムを使用するために必要な追加のポインターが気に入らない場合は、このバリアントを試してください。まず、Xを抽象基本クラスとして定義します。

// x.hpp, guard #defines elided

class X
{
protected:
    X();

public:
    virtual ~X();

public:
    static X * create();
    virtual void some_method( int blah ) = 0;
};

Yはここでは機能しないことに注意してください。次に、Xから派生するimplクラスを作成します。

 #include "Y.hpp"
    #include "X.hpp"

class XImpl 
: public X
{
    friend class X;

private:
    XImpl();

public:
    virtual ~XImpl();

public:
    virtual void some_method( int blah ) = 0;

private:
    boost::scoped_ptr< Y< int > > m_y;
};

Xはファクトリ関数create()を宣言しました。XImplを返すには、これを実装します。

// X.cpp

#include "XImpl.h"

X * X::create()
{
    return new XImpl();
}

Xのユーザーは、y.hppを含まないX.hppを含めることができます。pimplに少し似ていますが、implオブジェクトへの明示的な追加ポインターがないものが得られます。

于 2011-03-28T14:45:37.227 に答える