1

私がやりたいことはこれです:

class Ba { }
class Da : public Ba {}
class Db : public Ba {}

class Bb // abstract base class that must not be a template.
{
    void Process()
    {
        list<Ba*>::iterator pos;
        // I known the derived class will also derive
        // from list<Dx*> where Dx derives from Ba
        for(pos = this->begin(); pos < this->end(); pos++)
            something(*pos);
    }
}

template<T> class L : public Bb , list<T*> // T is always derived from Ba
{
}

しかし、それは無効です。それで、次善の策は何ですか?

別の定式化では、型が実行時までわからないグローバルProcessget に渡されたポインターがあります。Bb

void GlobalProcess(Bb* bb) // don't know what kind of Bb (which L) got passed in.
{
        list<Ba*>::iterator pos;
        // I known the derived class will also derive
        // from list<Dx*> where Dx derives from Ba
        for(pos = bb->begin(); pos < bb->end(); pos++)
            something(*pos);
}

これを行う理由はいくつかありますが、その約半分は C++ システムの限界を感じているためです。


私が思い付くことができる問題の最も単純な形式は、list<D*>コンパイル時に使用できるすべての情報がDfrom から派生したものである from コードを反復処理する必要があることですB。この状況はlist<D>、ポインターを介して抽象基本クラスに渡された場合、またはコード自体が基本クラスにある場合に発生する可能性があります。

list<D*>::iteratorのみから派生した場合、これはすべて正常に機能しますlist<B*>::iterator

4

4 に答える 4

8

まず第一に、から継承しないでくださいstd::list<>。STL のクラスは (いくつかの小さな例外を除いて) から派生することを意図していません。それらには仮想メンバーがありません。そうすることによる利点はありません。問題が発生するだけです。

あなたの質問は少し曖昧で紛らわしいです。しかし、あなたがこれをしたいと仮定してください:

struct A {};
struct B : public A {};
std::list<B> l;

for(std::list<A>::iterator it = l.begin(); it != l.end(); ++it) {
    whatever(*it);
}

明らかにこれは無効です。しかし、次のようなことができます:

struct A {};
struct B : public A {};
std::list<B> l;


void whatever(A& a) {
    // do something with an A
}

std::list<B> l;
for(std::list<B>::iterator it = l.begin(); it != l.end(); ++it) {
    whatever(*it);
}

またはさらに良い:

struct A {};
struct B : public A {};
std::list<B> l;

void whatever(A& a) {
    // do something with an A
}

std::for_each(l.begin(), l.end(), whatever);

編集:

必要なことを行うには、次のように少し追加のテンプレート作業を行うだけです。

class Ba {};
class Da : public Ba {};
class Db : public Ba {};

template <class T>
class Bb {
    void Process() {
        list<T>::iterator pos;
        for(pos = begin(); pos < end(); pos++)
            something(*pos);
    }
}

template <class T>
class L : public Bb<T>, std::list<T> {
}

または、次のように Process と interface を作成できます。

template <class T>
class Bb {
public:
    virtual void Process() = 0;
}

template <class T>
class L : public Bb<T>, std::list<T> {
public:
    virtual void Process() {
        list<T>::iterator pos;
        for(pos = begin(); pos < end(); pos++)
            something(*pos);
    }
}

ただし、継承はほぼ確実にエラーにつながるため、継承しない方がはるかに良いでしょう。クラスのメンバーになったstd::list<>ほうがいいです。std::list<T>L

于 2009-09-24T01:08:43.433 に答える
3

基本的に、C++ で必要なものを正確に取得することは不可能です。言語はこのようには機能しません。どちらが効果的に適用したいルールであるかをX : Y意味するものではありません。C<X> : C<Y>なぜそうしないのですか?いくつかの理由があります:

  • C はまったくコンテナーではない可能性があります (知る方法はありません)。
  • テンプレートの特殊化 (C<X>とはまったく異なるコードを持つことができますC<Y>)。
  • (この問題は C++ に限定されないため、疑似コード)

    class Base
    {
       void method();
    };
    
    class Child : public Base
    {
       void newmethod();
    };
    
    list<Base> base_list;
    list<Child> child_list;
    
     void safe(list<Base> list)
     {
        for(it in list)
        { it->method(); }
     }
    
     void still_safe(list<Child> list)
     {
        for(it in list)
        { it->newmethod(); }
     } 
    
     safe(base_list);
     safe(child_list);
     still_safe(child_list);
    
     void unsafe(list<Base> list)
     {
        list.push_back(new Base);
     }
    
     unsafe(base_list); // safe
     safe(base_list);
     unsafe(child_list); // maybe we get away with this
     still_safe(child_list); // KA BOOM! because the last item in child_list can't newmethod()
    

一部の言語では、これをより限定的な形式で (機能し、安全な方法で) 行うことができますが、あなたが求めているものを具体的に許可するものは思いつきません。http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29も参照してください。

この種の問題に対する C++ での本当の解決策は、ポリモーフィズムを利用することです (リスト/イテレータ型でパラメーター化されたテンプレートまたは C および D で動作する仮想メソッドを使用するかどうかに関係なく)。

于 2009-09-24T03:42:09.583 に答える
0

反復コードが基本クラスにある場合、反復するリストも基本クラスにある必要があります。これは、派生クラスのタイプを決定したいので、リストにどのタイプのオブジェクトを含める必要があるかという問題につながります。この問題を解決するために、多相型を格納できるリストを効果的に持つ共有ポインタを格納できます。コードは次のとおりです。

#include <list>
#include <boost/shared_ptr.hpp>

class Ba { };
typedef boost::shared_ptr<Ba> BaSP;

class Da : public Ba {};
class Db : public Ba {};

void something(BaSP& a) {
    // do something with an Ba
}

class Bb // abstract base class that must not be a template.
{
protected:
    std::list<BaSP> list_;
    void Process()
    {
        std::list<BaSP>::iterator pos;
        for(pos = list_.begin(); pos != list_.end(); pos++)
             something(*pos);
    }
};

class L : public Bb 
{
    L() { 
        // the list can store Da and Db objects 
        boost::shared_ptr<Da> da;
        list_.push_back(da);
        boost::shared_ptr<Db> db;
        list_.push_back(db);
    }
};

これは、投稿したコードの次善の策です。達成しようとしていることの全体像を教えていただければ、より良いデザインを思いつくかもしれません。

于 2009-09-24T02:09:46.960 に答える
0

これは、処理コードをテンプレート化されたクラスに移動することで機能するはずです。

class Bb
{
    virtual void Process() = 0;
}

template<T> class L : public Bb , list<T> // T is always derived from Ba
{
    virtual void Process()
    {
        list<T>::iterator pos;
        for(pos = begin(); pos < end(); pos++)
            something(*pos);
    }
}

このようにして、イテレータの正しい型を指定し、ジェネリックorProcess()から呼び出すことができます。*Bb&Bb

編集:

本当に仮想関数を使用したくなく、すべての作業を classBbで実行したい場合は、アクセスBbする必要がある について知る必要がlist<T>あります。したがって、何らかの形で型について知る必要がTあり、さまざまな型のジェネリックではなくなりTます。

いくつかの異なるタイプTがある場合、どこかでポリモーフィズムを使用する必要があります。これは、いくつかの仮想関数またはテンプレートを使用することを意味します。

于 2009-09-24T01:18:00.663 に答える