7

次のようなものがあるとしましょう。

class A { virtual void g() = 0 }

class B : public A { virtual void g() { ... } }
class C : public A { virtual void g() { ... } }

... f(bool x)
{
  if (x) { return B(); } else { return C(); }
}

bool get_boolean();

int main()
{
  bool b = get_boolean();
  ... x = f(b);
  x.g();
}

newへの呼び出しなしで、つまりスタック上でのみ上記のようなことを行う方法はありますか?

4

10 に答える 10

7

動的割り当てを回避する簡単な方法は、静的割り当てを使用することです。これは、動的割り当てとは可能な限り反対です。ただし、スレッド化されていないプログラムであっても、コードの 2 つ以上の部分がそれぞれ静的に割り当てられたオブジェクトを「所有」していると思われる状況に陥る可能性があるため、慎重に行う必要があります。さらに悪いことに、そのような本質的にグローバルな変数(シングルトンとして偽装されている場合や、以下のコードでローカルな静的変数として表示されている場合でも) は、本質的にスパゲッティ通信の中心的なハブとして機能し、カオスを誘発する情報が想像もできない場所間で自由に伝播されます。あなたのコントロール。

したがって、静的割り当てスキームにはいくつかの欠点があります… :-)

しかし、そこから始めましょう:

// Using static allocation.

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x )
{
    static B    theB;
    static C    theC;

    if( x ) { theB = B(); return theB; } else { theC = C(); return theC; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    A& x = f( b ); 
    x.g();
}

静的割り当てスキームの誤った所有権の欠点を回避するために、C++ 自動割り当てを使用して、スタック上にストレージを提供できます( C++ 自動割り当ては定義上スタックであり、LIFO 割り当てスキームです)。しかし、これはストレージを関数に渡すことを意味します。関数は、関連するオブジェクトへの参照を返すことができます。

// Using automatic storage (the stack)

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x, B& b, C& c )
{
    if( x ) { b = B(); return b; } else { c = C(); return c; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    B   objBStorage;
    C   objCStorage;
    A&  x   = f( b, objBStorage, objCStorage ); 
    x.g();
}

しかし、副作用のある構築などの問題を無視することを選択した場合でも、つまり、クラスBおよびCがそのようなスキームでうまく機能するように設計されていると軽々しく想定している場合でも、上記はstorage を浪費します。Bしたがって、インスタンスが大きい場合Cは、C++ の機能を使用して既存のストレージにオブジェクトを構築することを検討できます。これは、配置 newとして知られています。メモリ アライメントの問題により、C++03 で正しく実行するのは少し困難ですが、C++11 では次のようにより優れたサポートが提供されます。

#include <iostream>
#include <memory>           // unique_ptr
#include <new>              // new
#include <type_traits>      // aligned_storage
using namespace std;

typedef unsigned char Byte;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A* f( bool const x, void* storage )
{
    return (x? static_cast<A*>( ::new( storage ) B() ) : ::new( storage ) C());
}

bool get_boolean() { return false; }

void destroyA( A* p ) { p->~A(); }

int main()
{
    enum{ enoughBytes = 
        (sizeof( B ) > sizeof( C ))? sizeof( B ) : sizeof( C ) };
    typedef aligned_storage< enoughBytes >::type StorageForBOrC;

    bool const b = get_boolean();
    StorageForBOrC storage;
    A* const pX = f( b, &storage );
    unique_ptr<A, void(*)(A*)> const cleanup( pX, destroyA );
    pX->g();
}

さて、私は上記のどれを選びますか?

厳しく制限されているが単純で瞬時の静的割り当てを選択するか、それともメモリを浪費する自動割り当てを選択するか、それとも… 最適化されているがやや複雑なインプレース オブジェクト構築を選択するか?

答えは、私ならどれも選びません!

マイクロ効率に焦点を当てる代わりに、私は明快さ正確さに焦点を当てます。正確を期すために、関数の結果にスマート ポインターを使用します。これが本当に速度を低下させていることが判明した場合は、おそらく専用の小さなオブジェクト アロケーターの使用を検討します。

結論として、小さなことを心配しないでください!:-)

于 2012-11-12T11:36:22.983 に答える
3

関数fオブジェクト内B()またはC()両方とも一時的であるため、値によってのみ返すことができfます。

多分boost::variantはあなたのためです。そうすれば、メソッドを virtual にしたり、共通の基本クラスから派生させたりする必要さえありません。

于 2012-11-12T08:54:42.820 に答える
0

とにかく、newを呼び出さずに、つまりスタック上でのみ、上記のようなことを行うことはできますか?

プレースメントnewを使用できます。これにより、オブジェクトのメモリ位置を指定できます(たとえばchar、スタックで宣言するバッファ内)。


例: http: //www.parashift.com/c++-faq-lite/placement-new.html

于 2012-11-12T08:57:24.163 に答える
0

関数の引数を使用するようにリファクタリングします。

class A { virtual void g() = 0 }

class B : public A { virtual void g() { ... } }
class C : public A { virtual void g() { ... } }

template<typename FunOb>
typename std::result_of<FunOb(A&)>::type f(bool x, FunOb fo)
{
  if (x) { B b; return fo(b); } else { C c; return fo(c); }
}

bool get_boolean();

int main()
{
  bool b = get_boolean();
  f(b, [](A& x) { x.g(); } );
}
于 2012-11-12T08:58:03.237 に答える
0

本当にすべてをスタックに保持したい場合は、Aインターフェイスを処理する関数を作成し、必要なオブジェクトで呼び出す方がよいでしょう:

void actionsWithA(A& a){
    a.g();
}

void doItWithB(){
    B b;
    actionsWithA(b);
}

void doItWithC(){
    C c;
    actionsWithA(c);
}

bool get_boolean();

int main()
{
    bool b = get_boolean();
    if(b) { doItWithB(); } else { doItWithC(); }
}
于 2012-11-12T13:00:48.397 に答える
0

オブジェクト参照を使用できますが、呼び出しが終了するとすぐにオブジェクトが削除されるため、スタックに作成されたオブジェクトへの参照を返すことはできません。

ここに1つの提案があります:

#include <iostream>

class A
{
public:
virtual void g() const = 0;
};

class B : public A
{
public:
virtual void g() const {std::cout << "B" << std::endl;};
};

class C : public A
{
public:
virtual void g() const {std::cout << "C" << std::endl;};
};

const A &f(bool b)
{
  if (b) return B(); else return C();
}

void doStuff(const A &a)
{
  a.g();
}

int main(void)
{
  doStuff(B()); //"B"
  doStuff(C()); //"C"

//  A &a = f(true); //ERROR! a will be deleted right after call is made, giving you a reference to an invalid object!

return 0;
}

別の解決策は、静的オブジェクトの参照を返すことです。オブジェクトは、そのメソッドを呼び出すすべてのインスタンスによって共有されることに注意してください。オブジェクトを変更する必要はありませんが、それ以外の場合は良い解決策ではありません。

#include <iostream>

class A
{
public:
virtual void g() const = 0;
};

class B : public A
{
public:
virtual void g() const {std::cout << "B" << std::endl;};
};

class C : public A
{
public:
virtual void g() const {std::cout << "C" << std::endl;};
};

const A &f(bool b)
{
static B ret1;
static C ret2;
   if (b) return ret1; else return ret2;
}


void doStuff(const A &a)
{
  a.g();
}

int main(void)
{
  doStuff(f(true)); //"B"
  doStuff(f(false)); //"C"

return 0;
}
于 2012-11-12T09:11:38.310 に答える
0

質問に欠けているものはありますか?以下のような単純なものではないのはなぜですか

void CallGFunc(A *obj){
   obj->g();
}

int main(){
    ....
    B b;
    C c;

    CallGFunc(booleanvar? &b:&c)
    ....
}

これは、あなたの望むことですか?

于 2012-11-12T09:00:00.537 に答える
0

ポリモーフィズムは参照でも機能しますが、参照によって一時を返すことはできないため、次のようになります。

A& f(bool x)
{
  static B b;
  static C c;
  if (x) { return b; } else { return c; }
}

A& x = f(b);
于 2012-11-12T08:54:34.600 に答える
0

あまり。問題は、コピー操作が多態的でないことです。関数が を返す場合、AのみAがコピーされます。(これはスライシングと呼ばれます。) コピーがなければ、スコープに基づくものではなく、オブジェクトの任意の有効期間が必要です。そして、一般的に利用可能な他の唯一の可能性については、動的割り当てです。

関数が一度だけ呼び出され、オブジェクトが多かれ少なかれシングルトンである場合は、静的インスタンスを使用できますが、両方を体系的に構築することを避けるために、おそらく別々のスコープで定義する必要があります。

A*
f( bool c )
{
    A* results = NULL;
    if ( c ) {
        static B b;
        results = &b;
    } else {
        static C c;
        results = &c;
    }
    return results;
}

そしてもちろん、コピーとポリモーフィズムを組み合わせるためのレター エンベロープ イディオムが常にあります。しかし、これにより、実際にはさらに動的な割り当てが行われることになり、それらがすべて「ボンネットの下」で発生したとしても、クライアント コードはそれらを認識しません。(オブジェクトが不変の場合、レター エンベロープ イディオムは参照カウント ポインターを使用し、追加の割り当てのほとんどを回避できます。)

于 2012-11-12T10:27:49.200 に答える