3

少し難しい条件で、子クラスのインスタンスをその親に動的にキャストする方法を見つけようとしています。

具体的には、次のようなオブジェクト階層があります (大幅に簡略化したので、意味をなさない場合は、簡略化が原因である可能性があります)。

class Object {
public:
  virtual ~Object() {}
};

// shown just to give an idea of how Object is used
class IntObject: public Object {
protected:
  int value;
public:
  IntObject(int v) { value = v; }
  int getValue() { return value; }
};

template <class T>
class ObjectProxy: public Object {
protected:
  T *instance;
public:
   ObjectProxy(T *instance): instance(instance) {}  
   T *getInstance() { return instance; }
};

このObjectProxyクラスは基本的にラッパーとして機能し、Object階層内で他の型を使用できるようにします。具体的には、クラス インスタンスへのポインターを保持し、後でインスタンスのメソッドを呼び出すときに使用できるようにします。たとえば、私が持っているとします:

class Parent {
protected:
  int a;
public:
  Parent(int v) { a = v; }
  virtual ~Parent() {}
  void setA(int v) { a = v; }
  int getA() { return a; }
};

class Child: public Parent {
protected:
  int b;
public:
  Child(int v1, int v2): Parent(v1) { b = v2; }
  void setA(int v) { b = v; }
  int getB() { return b; }
};

次の状況でそれらを使用する可能性があります。

template <typename C>
void callFn(std::list<Object *> &stack, std::function<void (C*)> fn) {
  Object *value = stack.front();
  stack.pop_front();

  ObjectProxy<C> *proxy = dynamic_cast<ObjectProxy<C> *>(value);
  if (proxy == nullptr) {
   throw std::runtime_error("dynamic cast failed");
  }

  fn(proxy->getInstance());
}

void doSomething(Parent *parent) {
  std::cout << "got: " << parent->getA() << std::endl;
}

int main() {
  std::list<Object *> stack;


  // this works
  stack.push_back(new ObjectProxy<Child>(new Child(1, 2)));
  callFn<Child>(stack, doSomething);

  // this will fail (can't dynamically cast ObjectProxy<Child> to ObjectProxy<Parent>)
  stack.push_back(new ObjectProxy<Child>(new Child(1, 2)));
  callFn<Parent>(stack, doSomething);   
}

上記のコメントで述べたように、このコードは既知の理由で失敗します。サンプル コードでは、呼び出しを避けるのは簡単callFn<Parent>(stack, doSomething)です。ただし、実際のコードでは、関数のシグネチャを使用して型を決定しています。親クラスのメソッドである場合は、テンプレート パラメーターに自動的に使用されます。

私の質問は、ObjectProxy のタイプのオブジェクトから ObjectProxy からの動的キャストを実現する方法があるかどうかです。callFn複雑さの一部は、 functionで、子の型ではなく親の型しか持たないという事実から来ています。

type-erasure via の使用を検討しましたboost::any(つまりObjectProxy、テンプレート化を停止し、代わりに を使用しました) が、動的キャスト ( is static )boost::any instanceに関してはまだ問題に遭遇しました。boost::any_castSOに関する言及を見つけましたdynamic_anyが、まだ適切に機能していません。

問題への助けや洞察は大歓迎です。

4

3 に答える 3

2

ObjectProxyのインスタンス化であるクラスは、ObjectProxyのパラメーター化で指定されたタイプと同じ階層を共有しないため、動的キャストは失敗します。私は役立つかもしれない2つのアプローチを見ます。1つは、ObjectProxyに指定された型に単一の共通基本クラスを共有させ、動的キャストをObjectProxyからインスタンスに移動します。

namespace approach2 {
struct object_t {
    virtual ~object_t() { }
};


struct required_base_t {
    virtual ~required_base_t() { }
};

class object_proxy_base_t : public object_t {
    required_base_t* instance_;
public:
    object_proxy_base_t(required_base_t* i) : instance_ (i) { }

    template <class T>
    T* cast_to() const
    {
        return dynamic_cast<T*>(instance_);
    }
};

template <class value_t>
class object_proxy_t : public object_proxy_base_t {
    value_t* instance_;
public:
    object_proxy_t(value_t* i)
    :   object_proxy_base_t (i),
        instance_ (i)
    {
    }
};

template <class value_t>
object_t* new_with_proxy(value_t const& value)
{
    return new object_proxy_t<value_t>(new value_t(value));
}

struct parent_t : required_base_t {
    virtual ~parent_t() { }
};

struct child_t : parent_t {
    virtual ~child_t() { }
};

void f()
{
    object_t* a = new_with_proxy(parent_t()); 
    object_t* b = new_with_proxy(child_t());

    std::cout
        << dynamic_cast<object_proxy_base_t*>(a)->cast_to<parent_t>() << '\n' // works
        << dynamic_cast<object_proxy_base_t*>(b)->cast_to<parent_t>() << '\n' // works
        ;
}

}

ObjectProxyが使用するすべてのタイプの基本クラスを変更できない場合、このアプローチは不可能です。これは、ObjectProxyのインスタンス化を、パラメーター化に使用されるタイプと同じ階層にする2番目のソリューションにつながります。

namespace approach3 {
struct object_t {
    virtual ~object_t() { }
};


struct empty_t {
    template <class T>
    empty_t(T*) { }
};

template <class value_t>
class object_proxy_t : public virtual object_t {
    value_t* instance_;
public:
    object_proxy_t(value_t* i) : instance_ (i) { }
};

template <class value_t, class base_t>
class object_proxy_sub_t :
    public object_proxy_t<value_t>,
    public base_t {

public:
    object_proxy_sub_t(value_t* i)
    :   object_proxy_t<value_t>(i),
        base_t (i)
    {
    }
};

template <class base_t, class value_t>
object_t* new_with_proxy(value_t const& value)
{
    return new object_proxy_sub_t<value_t, base_t>(new value_t(value));
}

struct parent_t {
    virtual ~parent_t() { }
};

struct child_t : parent_t {
    virtual ~child_t() { }
};

void f()
{
    object_t* a = new_with_proxy<empty_t>(parent_t()); 
    object_t* b = new_with_proxy<object_proxy_t<parent_t> >(child_t());

    std::cout
        << dynamic_cast<object_proxy_t<parent_t>*>(a) << '\n' // works
        << dynamic_cast<object_proxy_t<parent_t>*>(b) << '\n' // works
        ;
}

}

このアプローチでは、関係するタイプの要件は少なくなりますが、階層の同期を維持するための作業が増えます。

于 2012-12-07T01:18:34.213 に答える
1

Bowie Owen の最初の回答に基づいて、指定された型が同じクラス (ライブラリ) から派生したものではない可能性が高いが、それを強制的に発生させることができることに気付きました。

struct ObjectProxyBaseType {
    virtual ~ObjectProxyBaseType() {}
};

template <class T>
class ObjectProxyType: public ObjectProxyBaseType, public T {
public:
  // allow construction via parameters
  template <typename... Args>
  ObjectProxyType(Args &&... args): T(std::move(args)...) {}

  // or construction via copy constructor
  ObjectProxyType(T *t): T(*t) {}

  virtual ~ObjectProxyType() {}
};

したがって、クラス Child がある場合、 のインスタンスを作成できます。ObjectProxyType<Child>これにより、 も継承されObjectProxyBaseTypeます。コードの残りの部分は、Bowie の提案に従います。

class ObjectProxy: public Object {
protected:
  ObjectProxyBaseType *instance;
public:
  template <typename T>
  ObjectProxy(ObjectProxyType<T> *i) {
    instance = i;
  }

  template <typename T>
  ObjectProxy(T *value) {
    instance = new ObjectProxyType<T>(value);
  }


  template <typename T>
  T *castTo() const {
    return dynamic_cast<T *>(instance);
  }
};

動作するコードの例:

int main() {
    std::list<Object *> stack;
    stack.push_back(new ObjectProxy(new Child(1, 2)));
    callFn<Child>(stack, doSomething);

    stack.push_back(new ObjectProxy(new Child(5, 6)));
    callFn<Parent>(stack, doSomething);
}
于 2012-12-07T16:31:19.407 に答える
0

私は最近、やや似たようなことをしなければなりませんでした。私は自分に合ったアプローチを使用しましたが、この場合は適切ではないかもしれません。あなたの裁量を使用してください。これは、あなた (またはこのコードを拡張する人) が、テンプレート パラメーターとして使用される階層について完全な知識を持っているという事実にかかっています。

したがって、これらの階層が次のようになっているとしましょう。

   クラス Parent1
   クラス Child1: public Parent1
   クラス Child11: public Child1
   ...
   クラス Parent2
   クラス Child2: public Parent2
   ...

次に、ホルダー クラスを作成します。単純な理由で少し複雑です。私のコンパイラは関数のデフォルトのテンプレート パラメータをサポートしていないため、ヘルパー構造体を使用して SFINAE を有効にしています。

このクラスは、すべての階層に属するオブジェクトを保持できる必要があります (基本クラス ポインターを介して)。

class TypeHolder
{
template<class T, class E=void>
struct GetHelper
{
    static T* Get(const TypeHolder* th) { return nullptr; }
            //you can actually add code here to deal with non-polymorphic types through this class as well, if desirable
};

template<class T>
struct GetHelper<T, typename std::enable_if<std::is_polymorphic<T>::value, void>::type>
{
    static T* Get(const TypeHolder* th)
    {
        switch(th->type)
        {
            case P1: return dynamic_cast<T*>(th->data.p1);
            case P2: return dynamic_cast<T*>(th->data.p2);
                            //and so on...
            default: return nullptr;
        }
    }
};

template<class T, class E=void>
struct SetHelper
{
    static void Set(T*, TypeHolder* th) { th->type = EMPTY; }
};

template<class T>
struct SetHelper<T, typename std::enable_if<std::is_polymorphic<T>::value, void>::type>
{
    static void Set(T* t, TypeHolder* th)
    {
        th->data.p1 = dynamic_cast<Parent1*>(t);
        if(th->data.p1) { th->type = P1; return; }

        th->data.p2 = dynamic_cast<Parent2*>(t);
        if(th->data.p2) { th->type = P2; return; }

                    //...and so on

        th->type = EMPTY;
    }
};

public:
TypeHolder(): type(EMPTY) { }

template<class T>
T* GetInstance() const
{
    return GetHelper<T>::Get(this);
}

template<class T>
void SetInstance(T* t)
{
    SetHelper<T>::Set(t, this);
}

private:
union
{
    Parent1* p1;
    Parent2* p2;
            //...and so on
} data;

enum
{
    EMPTY,
    P1,
    P2
            //...and so on
} type;
};

ところで、SFINAE トリックが必要な理由は、非ポリモーフィック型ではコンパイルされない dynamic_casts のためです。

あとは、クラスを少し変更するだけです:)

class ObjectProxyBase
{
public:
    virtual const TypeHolder& GetTypeHolder() const = 0;
};

template<class T>
class ObjectProxy: public Object, public ObjectProxyBase
{
    T* instance;

    static TypeHolder th; //or you can store this somewhere else, or make it a normal (but probably mutable) member
public:
    ObjectProxy(T* t): instance(t) { }

    T* getInstance() const { return instance; }

    const TypeHolder& GetTypeHolder() const { th.SetInstance(instance); return th; }

    //... and the rest of the class
};

template<class T>
TypeHolder ObjectProxy<T>::th;

このコードが実際に正しいことを願っています。ほとんどの場合、ブラウザー ウィンドウに入力したからです (私は別の名前を使用しました)。

そして、最後の部分である関数です。

template <typename C>
void callFn(std::list<Object *> &stack, std::function<void (C*)> fn) {
    Object *value = stack.front();
    stack.pop_front();

    ObjectProxyBase *proxy = dynamic_cast<ObjectProxyBase *>(value);
    if (proxy == nullptr) {
        throw std::runtime_error("dynamic cast failed");
    }

    C* heldobj = proxy->GetTypeHolder().GetInstance<C>(); //I used to have a dynamic_cast here but it was unnecessary
    if (heldobj == nullptr) {
        throw std::runtime_error("object type mismatch");
    }

    fn(heldobj);
}

このアプローチは階層に対してのみ使用する必要があり、他の場合dynamic_castには直接使用できます (基本的に、両方を試して、どちらかが成功するかどうかを確認する必要があります)。ObjectProxy<C>*

これが少なくとも少し役立つことを願っています。

于 2012-12-07T02:16:21.493 に答える