8

スタックにポリモーフィック オブジェクトを割り当てるにはどうすればよいですか? 私は似たようなことをしようとしています (新しいヒープ割り当てを回避しようとしています)?:

A* a = NULL;

switch (some_var)
{
case 1:
    a = A();
    break;
case 2:
    a = B(); // B is derived from A
    break;
default:
    a = C(); // C is derived from A
    break;
}
4

12 に答える 12

7

条件付きブロック内に作成された自動オブジェクトまたは一時オブジェクトは、その存続期間を包含ブロックに延長できないため、そのように機能するように単一の関数を構造化することはできません。

ポリモーフィックな動作を別の関数にリファクタリングすることをお勧めします。

void do_something(A&&);

switch (some_var)
{
case 1:
    do_something(A());
    break;
case 2:
    do_something(B()); // B is derived from A
    break;
default:
    do_something(C()); // C is derived from A
    break;
}
于 2012-08-15T17:47:16.457 に答える
7

免責事項:これが良い解決策だとは絶対に思いません。適切な解決策は、設計を再考するか (可能性の数が限られているため、オブジェクト指向ポリモーフィズムはここでは保証されないのでしょうか?)、または参照によってポリモーフィック オブジェクトを渡す 2 番目の関数を使用することです。

しかし、他の人々がこのアイデアについて言及したが、詳細が間違っていたので、私はこの回答を投稿して、それを正しくする方法を示しています. うまくいけば、私はそれを正しく理解します。

可能なタイプの数が制限されていることは明らかです。これは、boost::variantきれいでなくても、識別された共用体が問題を解決できることを意味します。

boost::variant<A, B, C> thingy = 
    some_var == 1? static_cast<A&&>(A())
    : some_var == 2? static_cast<A&&>(B())
    : static_cast<A&&>(C());

静的ビジターのようなものを使用できるようになったという事実は、これが OO ポリモーフィズムの適切な使用法ではないと私に思わせ続けるものである場合の 1 つです。

既製のソリューションの代わりに、他の回答で提案されているように手動で新しい配置を使用する場合、プロセスで通常の自動オブジェクトのプロパティの一部が失われるため、注意が必要なことがいくつかあります。

  • コンパイラは、適切なサイズと配置を提供しなくなりました。
  • デストラクタへの自動呼び出しがなくなりました。

C++11 では、これらはどちらもaligned_unionunique_ptrでそれぞれ簡単に修正できます。

std::aligned_union<A, B, C>::type thingy;
A* ptr;
switch (some_var)
{
case 1:
    ptr = ::new(&thingy.a) A();
    break;
case 2:
    ptr = ::new(&thingy.b) B();
    break;
default:
    ptr = ::new(&thingy.c) C();
    break;
}
std::unique_ptr<A, void(*)(A*)> guard { ptr, [](A* a) { a->~A(); } };
// all this mechanism is a great candidate for encapsulation in a class of its own
// but boost::variant already exists, so...

これらの機能をサポートしていないコンパイラの場合は、代替手段を入手aligned_storageできalignment_ofますaligned_unionunique_ptrある種のスコープ ガード クラスに置き換えることができます。

これは邪魔にならないので、明確にするために、これを行わずに一時的なものを別の関数に渡すか、設計全体を再検討してください。

于 2012-08-15T17:39:52.893 に答える
4

B が基本型の場合、D1、D2、および D3 は派生型です。

void foo()
{
    D1  derived_object1;
    D2  derived_object2;
    D3  derived_object3;
    B *base_pointer;

    switch (some_var)
    {
        case 1:  base_pointer = &derived_object1;  break;
        ....
    }
}

3 つの派生オブジェクトのスペースを無駄にしたくない場合は、メソッドを 2 つの部分に分割できます。必要なタイプを選択する部分と、それに対応するメソッドの部分です。必要な型を決定したら、そのオブジェクトを割り当てるメソッドを呼び出し、そのオブジェクトへのポインターを作成し、メソッドの後半を呼び出して、スタックに割り当てられたオブジェクトに対する作業を完了します。

于 2012-08-15T17:35:11.357 に答える
2

多態的なローカル変数を作成することはできません

BのサブクラスはAよりも多くの属性を持つ可能性があり、より多くの場所を占める可能性があるため、多態的なローカル変数を作成することはできませんA。そのため、コンパイラは の最大のサブクラスのために十分なスペースを予約する必要がありAます。

  1. 数十のサブクラスがあり、そのうちの 1 つに多数の属性がある場合、これは多くのスペースを浪費します。
  2. パラメーターとして受け取ったサブクラスのインスタンスをローカル変数にA入れ、コードを動的ライブラリーに入れる場合、それにリンクするコードは、ライブラリー内のものよりも大きなサブクラスを宣言する可能性があるため、コンパイラーはとにかく、スタックに十分なスペースを割り当てていないでしょう。

そのため、自分でスペースを割り当てます

配置 newを使用すると、他の方法で割り当てたスペースでオブジェクトを初期化できます。

Aただし、これらの手法は多くの余分なスペースを使用する可能性があり、説明した型よりも大きい、コンパイル時に不明なサブクラスへの参照 (ポインター) が与えられた場合は機能しません。

私が提案する解決策は、特定のサブクラスのスタック割り当てインスタンスへのポインターを使用して提供された関数を呼び出す、各サブクラスに一種のファクトリ メソッドを用意することです。提供された関数のシグネチャに追加の void* パラメータを追加したので、任意のデータを渡すことができます。

@MooingDuck は、以下のコメントで、テンプレートと C++11 を使用してこの実装を提案しました。C++11 の機能の恩恵を受けられないコード、またはクラスの代わりに構造体を使用する単純な C コードでこれが必要な場合 (struct B最初のフィールドが type のstruct A場合、「サブ構造体」のように操作できます) of A)、以下の私のバージョンはトリックを行います (ただし、タイプセーフではありません)。

このバージョンは、ファクトリのようなメソッドを実装している限り、新しく定義されたサブクラスで動作し、uglyこの中間関数に必要な戻りアドレスとその他の情報に加えて、要求されたインスタンスのサイズに一定量のスタックを使用します。クラスですが、最大のサブクラスのサイズではありません (そのサブクラスを使用することを選択しない限り)。

#include <iostream>
class A {
    public:
    int fieldA;
    static void* ugly(void* (*f)(A*, void*), void* param) {
        A instance;
        return f(&instance, param);
    }
    // ...
};
class B : public A {
    public:
    int fieldB;
    static void* ugly(void* (*f)(A*, void*), void* param) {
        B instance;
        return f(&instance, param);
    }
    // ...
};
class C : public B {
    public:
    int fieldC;
    static void* ugly(void* (*f)(A*, void*), void* param) {
        C instance;
        return f(&instance, param);
    }
    // ...
};
void* doWork(A* abc, void* param) {
    abc->fieldA = (int)param;
    if ((int)param == 4) {
        ((C*)abc)->fieldC++;
    }
    return (void*)abc->fieldA;
}
void* otherWork(A* abc, void* param) {
    // Do something with abc
    return (void*)(((int)param)/2);
}
int main() {
    std::cout << (int)A::ugly(doWork, (void*)3);
    std::cout << (int)B::ugly(doWork, (void*)1);
    std::cout << (int)C::ugly(doWork, (void*)4);
    std::cout << (int)A::ugly(otherWork, (void*)2);
    std::cout << (int)C::ugly(otherWork, (void*)11);
    std::cout << (int)B::ugly(otherWork, (void*)19);
    std::cout << std::endl;
    return 0;
}

それまでに、単純な のコストを上回っている可能性があると思いますmalloc

于 2012-08-15T17:34:28.740 に答える
1

新しい配置でそれを行うことができます。これにより、バッファに含まれるメモリのスタックにアイテムが配置されます。ただし、これらの変数は自動ではありません。マイナス面は、デストラクタが自動的に実行されないことです。スコープ外になったときに、作成したのと同じように適切に破棄する必要があります。

デストラクタを手動で呼び出す代わりに、次に示すように型をスマート ポインタでラップすることも合理的です。

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

class B : public A {};
class C : public B {};

template<class T>
class JustDestruct
{
public:
   void operator()(const T* a)
   {
      a->T::~T();
   }
};

void create(int x)
{
    char buff[1024] // ensure that this is large enough to hold your "biggest" object
    std::unique_ptr<A, JustDestruct<T>> t(buff);

    switch(x)
    {
    case 0:
       ptr = new (buff) A();
       break;

    case 1:
       ptr = new (buff) B();
       break;

    case 2:
       ptr = new (buff) C();
       break;
    }

    // do polymorphic stuff
}
于 2012-08-15T17:40:12.270 に答える
0

newでヒープ割り当てを回避しようとしていますか?

その場合は、通常どおりスタック上にオブジェクトを作成し、ベースポインタにアドレスを割り当てます。ただし、これが関数内で行われる場合は、アドレスを戻り値として渡さないでください。関数呼び出しが戻った後にスタックが巻き戻されるためです。

だからこれは悪いです。

A* SomeMethod()
{
    B b;
    A* a = &b; // B inherits from A
    return a;
}
于 2012-08-15T17:42:02.080 に答える
0

ポリモーフィズムは値では機能しません。参照またはポインターが必要です。一時オブジェクトへの const 参照をポリモーフィックに使用でき、スタック オブジェクトの有効期間があります。

const A& = (use_b ? B() : A());

オブジェクトを変更する必要がある場合は、動的に割り当てるしかありません (一時オブジェクトを非 const 参照にバインドできる Microsoft の非標準拡張機能を使用している場合を除く)。

于 2012-08-15T17:37:48.563 に答える
0

それ可能ですが、クリーンに行うには多大な労力が必要です (つまり、新しく公開された raw バッファーを手動で配置する必要はありません)

Boost.Variantのようなものを見ています。これは、型を基本クラスといくつかの派生クラスに制限し基本型へのポリモーフィック参照を公開するように変更されています。

このこと ( PolymorphicVariant ?) は、すべての配置の新しいものをラップします (また、安全な破棄を処理します)。

それが本当にあなたが望むものなら、私に知らせてください。ただし、この動作が本当に必要でない限り、Mike Seymour の提案はより実用的です。

于 2012-08-15T17:50:40.390 に答える
0

char配列と配置の組み合わせnewが機能します。

char buf[<size big enough to hold largest derived type>];
A *a = NULL;

switch (some_var)
{
case 1:
    a = new(buf) A;
    break;
case 2:
    a = new(buf) B;
    break;
default:
    a = new(buf) C;
    break;
}

// do stuff with a

a->~A(); // must call destructor explicitly
于 2012-08-15T17:40:28.980 に答える
0

あなたの質問に厳密に答えるために-あなたが今持っているものはまさにそれを行います-つまりa = A();anda = B()a = C()、しかしこれらのオブジェクトはスライスされています。

あなたが持っているコードでポリモーフィックな動作を実現するには、残念ながらそれは不可能です。コンパイラは、オブジェクトのサイズを事前に知る必要があります。参照またはポインタがない限り。

pointerを使用する場合は、ぶら下がっていないことを確認する必要があります。

A* a = NULL;

switch (some_var)
{
case 1:
    A obj;
    a = &obj;
    break;
}

obj範囲外のため動作しません。したがって、次のものが残ります。

A* a = NULL;
A obj1;
B obj2;
C obj3;
switch (some_var)
{
case 1:
    a = &obj1;
    break;
case 2:
    a = &obj2;
    break;
case 3:
    a = &obj3;
    break;
}

これはもちろん無駄です。

参照の場合は、作成時に割り当てる必要があり、(参照でない限り) 一時変数を使用できないため、少し注意が必要ですconst。したがって、おそらく永続的な参照を返すファクトリが必要になるでしょう。

于 2012-08-15T17:40:29.960 に答える