3

私はC++を初めて使用し、Java / C#を使用しています。

JavaとC#では、クラスを作成し、それから別のクラスを継承させ、その関数をオーバーライドできることを知っています。次に、親クラスのリストを作成し、このリストに子クラスのオブジェクトを挿入できます。その後、オーバーライドされた機能を使用できます。

例:

public class Parent
{
  public virtual void test()
  {
    Console.WriteLine("test");
  }
}

public class Child : Parent
{
  public override void test()
  {
    Console.WriteLine("test2");
  }
}

使用法:

List<Parent> tests = new List<Parent>();
tests.Add(new Child());
tests[0].test();

出力:

test2

C ++では、これをstd::vectorで行うと、子のメンバー関数ではなく、親のメンバー関数が呼び出されます。

C ++で上記を行うにはどうすればよいですか?

4

6 に答える 6

8

ここには2つの問題があるように感じます。1つは構文上の問題であり、他の人はすでに対処しています。ただし、 Java / C#コードをC++で書き込もうとするという根本的な問題もあるようです。これは構文上の問題が何であれ悲惨につながるので、ここでこれに対処しようとします。

C ++では、ベクトルを使用してこれを行うと、その親の関数が呼び出されます。上記の例をC++で実行するにはどうすればよいですか?

JavaとC#は、すべてにオブジェクト指向のパラダイムを使用します。C ++は、 C++がマルチパラダイム言語であるという点で異なります。これは、(多かれ少なかれ)構造化、OO、汎用、機能などのプログラミングパラダイムをサポートします。パラダイムを自由に組み合わせることができ、C++はそれを行う場所で最も明るく輝きます。

STLから派生した標準ライブラリの一部、つまり、コンテナ、アルゴリズム、イテレータは、OOではありません。彼らはジェネリックプログラミングを適用しています。その属性の1つは、コンテナは通常(例外はありますが、標準ライブラリ自体にはありません) 、参照ではなくを格納することです。ただし、ポリモーフィズムは、少なくとも実行時のポリモーフィズムは、参照(または、構文的には、意味的には参照でもあるポインター)に対してのみ機能します。

がある場合、これはヒープ上のどこかにあるオブジェクトへの参照ではなく、std::vector<base_class> vc実際の値を格納します。オブジェクトをそのようなコンテナに入れると、オブジェクトは実際にコンテナにコピーされます。derived_classオブジェクトを入れると、スライスされます。つまり、そのbase_class一部のみがコンテナにコピーされ、すべてのderived_class部分が無視されます。base_class次に、JavaやC#のように、ヒープのどこかにある派生クラスオブジェクトへの基本クラス参照ではなく、コンテナ内の実際のオブジェクトになります。
そのため、そのオブジェクトでメンバー関数を呼び出すと、基本クラスになります。関数を呼び出す派生クラスオブジェクトはありません。

C ++では、OOPを使用する場合、通常、派生クラスオブジェクト(つまりnew derived_class())を動的に割り当て、それらを基本クラスポインタに割り当てる必要があります。これに伴う問題は、C ++にはガベージコレクションがないため、これらのポインターとそこから作成されたすべてのコピーを追跡し、最後のポインターが破棄される直前にオブジェクトを明示的に削除する必要があることです。これは非常にエラーが発生しやすく、手動で行う必要があります。そのため、今日では誰もがスマートポインタにこれを自動的に行わせるようになっています。

だからあなたが欲しいのは、オブジェクトstd::vector<smart_ptr<base_class>>を入れることです。new derived_class()シンボリックが何smart_ptrを指すかは、ニーズによって異なります。それらのオブジェクトへのポインタをそのコンテナ以外の場所に格納することを計画している場合std::unique_ptrstd::tr1::unique_ptrコンパイラがC ++ 03のみをサポートしているboost::unique_ptr場合、またはそれをサポートしていない場合)が理想的です。そのようなポインタを自由に回して、最後のポインタがいつ範囲外になるかを追跡するのでstd::shared_ptrあれば、より良いでしょう。


さて、これはすべて、追加する必要があると感じています。これをOOの方法で行う必要はまったくないかもしれません。JavaとC#があなたを閉じ込めたと考えている厳格なOOを手放すことができれば、はるかに優れた設計があるかもしれません。

異なるコンテンツのコンテナを同じアルゴリズムに渡すことができるようにポリモーフィズムを採用する場合は、ジェネリックプログラミングを採用する方がはるかに優れている可能性があります

template<typename FwdIt>
void do_something(FwdIt begin, FwdIt end)
{
  while(begin != end)
    if(begin->foo() == bar()) // whatever
      begin->baz();           // whatever
}

std::vector<some_class> vs;
std::vector<other_class> vo;
std::deque<other_class> do;

// ...

do_something(vs.begin(), vs.end());
do_something(vo.begin(), vo.end());
do_something(do.begin(), do.end());

これは、引数を取らず、返されるものと同等の何かを返すメンバーがあり、引数も受け取らないメンバーを持つすべてのタイプ(ここではsome_class)で機能します。(それらを持たないタイプを使用しようとすると、コンパイラーはあなたに吠えます。)foo()bar()baz()

于 2012-06-25T12:51:52.607 に答える
3

JavaやC#とは異なり、C++はデフォルトで値のセマンティクスを使用します。に は、ポインタや参照ではなく、タイプstd::vector<Parent>の実際のオブジェクトが含まれます。Parentベクトルに挿入すると、挿入するオブジェクトがコピーされ、Parent タイプのオブジェクトにコピーされます。(オブジェクトはタイプを変更できません。)これはスライシングと呼ばれます。

C ++でポリモーフィズムを使用する場合は、参照セマンティクスが必要であることを明示的に指定する必要があります。ポインターと参照の両方が参照セマンティクスを提供し、「スマートポインター」(他のクラスへのポインターのように動作するクラス)を定義することができます。参照は、標準コンテナで必要なコピー/代入セマンティクスをサポートしていないため、コンテナをインスタンス化するために使用することはできません。したがって、コンテナがポリモーフィックオブジェクトを保持する場合は、ポインタを含むように定義する必要があります。それで:

std::vector<ValueType> v;
v.push_back( ValueType() );         //  no new

しかし

std::vector<BaseType*> v;
v.push_back( new DerivedType() );   //  dynamic allocation.

スライスが原因で、ポリモーフィズムとコピー/代入はうまく連携しません。通常、階層のベースとして設計されたクラスでは、コピー/代入をブロックします。

また、基本クラスへのポインタを介してオブジェクトを管理する場合、デストラクタは仮想である必要があります。

class Parent
{
public:
    virtual ~Parent() {}
    //  ...
};

そうしないと、オブジェクトを(そのベースへのポインタを介して)削除しようとしたときに、未定義の動作に遭遇します。

于 2012-06-25T13:11:13.787 に答える
1

test()クラスが呼び出されていることを確認するために、クラスで作成する必要がありvirtualます。ParentChildtest()

于 2012-06-25T12:13:22.807 に答える
1

boost::ptr_containerライブラリはあなたにとって非常に役立つようです。これは(スマート)ポインターのベクトルと同じように機能しますが、構文をそのように使用するように設計されているという追加の利点があります。

したがって、たとえば、次のことができます。

typedef boost::ptr_vector<AbstractClass> PolyVector;

PolyVector polyVect;
polyVect.push_back( std::unique_ptr( new ChildClassA() ) );
polyVect.push_back( std::unique_ptr( new ChildClassB() ) );
polyVect.push_back( std::unique_ptr( new ChildClassC() ) );

BOOST_FOREACH( PolyVector::value_type item, polyVect)
    item.memberFunction( x );

これは、仮想の派生クラスの実装を呼び出しますmemberFunction

于 2012-06-25T13:14:58.340 に答える
0

overrideC++にはキーワードがありません。オーバーライドされたメソッドをとして再宣言するだけvirtualです。

于 2012-06-25T12:02:00.577 に答える
0

C ++では、これは次のようになります。

MyList<Parent*>* tests = new MyList<Parent*>();
tests->Add(new Child());
tests->test();

親ではなく子の関数を呼び出すC++でポリモーフィック機能を呼び出すには、親クラスを指すか参照するポインターまたは参照を使用する必要があり、クラスメソッド自体をvirtual親と同様に宣言する必要があります。子クラスの宣言。

MyListこのようなrawポインターを使用すると、オブジェクトに渡されるポインターを「所有」する(または所有する必要がある)という事実を補正しない場合、深刻なメモリリークが発生する可能性があることに注意してください。所有権があいまいな場合は、特に注意するか、のようなものを使用する必要がありますstd::shared_ptr<T>。たとえば、std::vectorrawポインタのようにSTLコンテナを使用することにした場合、コンテナは各ポインタに割り当てられたメモリを「所有」せず、コンテナが破棄されたときに、各ポインタが指すメモリを解放しません。そのメンバーの、厄介なメモリリークが発生します。

ところで、これはC++に関する非常に重要なポイントです...C#/ Javaとは異なり、C++は暗黙的なポインターではなく明示的なポインターを使用します。したがって、オブジェクトがポインタではないように宣言した場合(つまり、スタック上の静的変数または「自動」変数である場合)、派生クラスインスタンスオブジェクトを親インスタンスにコピーすると、「スライス」になります。 "派生オブジェクトの親部分をオフにし、派生オブジェクトの親部分をコピーするだけです。それはあなたが望むものではありません。ポリモーフィックな動作が必要なため、親クラスタイプへのポインタまたは参照を使用する必要があります。

たとえば、ポリモーフィックな動作の実際の例を次に示します。

#include <iostream>

//polymorphic base
struct test
{
    virtual void print() { std::cout << "I'm the parent" << std::endl; }
};

//derived type
struct derived : public test
{
    virtual void print() { std::cout << "I'm the derived" << std::endl; }
};

int main()
{
    test* a = new test;
    test* b = new derived;

    a->print();
    b->print();  //calls derived::print through polymorphic behavior

    return 0;
}
于 2012-06-25T12:02:12.450 に答える