web-dev-qa-db-ja.com

インスタンスがスタック上にのみ存在することを強制する方法はありますか?

スタック上でインスタンス化することだけが必要なC++クラスがあります。私はAPIを使用して、独自のガベージコレクションが付属する別の(インタープリター型)言語で開発されたコンテンツにアクセスしています。この言語のメカニズムは、参照を見つけたコンテンツをスタックに残すのに十分な知識があります。このネイティブクラスにはそのような参照が含まれているため、正しい動作を行うには、ネイティブC++クラスのユーザーが行うことが非常に重要です。そのインスタンスを他の場所に割り当てようとしないでください。

クラスのインスタンスにnewが割り当てられるのを禁止したいだけではありません(必要なのがそれだけの場合は、クラスのnew演算子をオーバーロードしてプライベートにするか、明示的に削除することができます。 C++ 11)ですが、クラスの静的インスタンスまたはグローバルインスタンスの可能性も禁止します。このクラスを安全にインスタンス化する唯一の有効な方法はスタック上にあるべきであり、どういうわけかそれを保証したいと思います。私の知る限り、newをプライベートにしたり削除したりしても、別のクラスが私のクラスをメンバー変数として宣言され、そのインスタンスがヒープに割り当てられるのを防ぐことはできません。

私が今これを管理している方法は、インスタンスがスタックでのみ使用されることを意図していることをユーザーにわかりやすく思い出させるために、クラスの名前の一部として「ローカル」という単語を含めることですが、もちろん、これはそうではありません。 tは実際にはコンパイラまたはその他のメカニズムによって強制されますが、より強制可能なソリューションをお勧めします。

理想的には、コンパイル時にこれを確認し、誤って使用するとコンパイルに失敗したいと思います。これが単純に不可能な場合でも、インスタンスの構築時に実行時に例外をスローすることは、許容できるフォールバックです。 C++ 11またはC++ 14で機能するソリューションは問題ありません。

この質問は間違いなく[〜#〜]ではない[〜#〜]と同じであることに注意してください this 1、 newでの割り当てを防ぎたかっただけです

31
markt1964

免責事項:「スタック」は私の知る限りc ++標準の一部ではなく、ASDV(自動保存期間変数)があります。 ABIはスタックを定義する場合があります。これらはレジスタに渡されることがあることに注意してください。あなたの場合は問題ないと思います。

CPS(継続渡しスタイル)ファクトリメソッドを定義します。

class A {
public:
   template<typename F, typename... Args>
   static auto cps_make(F f, Args&&... args) {
      return f(A(std::forward<Args>(args)...));
   }
private:
   A(/* ... */) {}
   A(const A&) = delete;
   A(A&&) = delete;
};

使用法:AとAのctorパラメーターを取得するラムダを渡します。例:.

return A::cps_make([&](A a) {
   /* do something with a */
   return true;
});

関数の引数は常に内部のASDVです。

コードのしくみ:cps_makeは、指定されたタイプのインスタンスを受け取るファンクター(通常はラムダ)を取ります。およびオプションのctorパラメーター。インスタンスを作成し(オプションのパラメーターをctorに転送することにより)、ファンクターを呼び出し、ファンクターが返すものを返します。ファンクターはC++ 11ではラムダになる可能性があるため、通常のコードフローを中断することはありません。

CPSの優れている点は、C++ 14で自動ラムダを使用するだけで静的ポリモーフィズムを実現できることです。cps_make()は、ほぼすべてのもの(階層、バリアント、任意など)を作成できます。次に、閉じた階層の仮想オーバーヘッドを節約します。通常のフロー用のラムダと、ctorが失敗する場合のラムダを使用することもできます。これは、例外が発生しない場合に便利です。

欠点は、現在、ラムダ内の外部スコープの制御フローステートメントを直接使用できないことです。/*ヒント:現在取り組んでいます。 * /

40
lorro

さて、これが私の見解です:

struct stack_marker
{
    thread_local static uint8_t* marker;
    uint8_t stk;

    stack_marker()
    {
        if (marker != nullptr)
        {
            throw std::runtime_error("second twice marker! don't do it");
        }
        marker = &stk;
    }
};

thread_local uint8_t* stack_marker::marker = nullptr;

void sort3(uint8_t* (&a)[3]); //sorts 3 pointers, see Gist

class only_on_stack
{
    uint8_t place;
public:
    NO_INLINE only_on_stack(int x)
    {
        uint8_t a;

        if (!stack_marker::marker)
        {
            // not initialized yet, either forgot to put stack_marker in main
            // or we are running before main, which is static storage

            //throw std::runtime_error("only on stack object created in non-stack");
            std::cout << x << ": I'm NOT on stack\n";
            return;
        }

        uint8_t* ptrs[] = {
            stack_marker::marker,
            &place,
            &a
        };

        sort3(ptrs);

        if (ptrs[1] == &place) // place must be in the middle
        {
            std::cout << x << ": I'm on stack\n";
        }
        else
        {
            //throw std::runtime_error("only_on_stack object created in non-stack");
            std::cout << x << ": I'm NOT on stack\n";
        }
    }
};

only_on_stack static_storage(1);
thread_local only_on_stack tl_storage(4);

int NO_INLINE stuff()
{
    only_on_stack oos(2);
}

int main()
{
    stack_marker mrk;
    stuff();
    auto test = new only_on_stack(3);
    tl_storage; // access thread local to construct, or gcc omits it
}

確かに、私のソリューションはそれらすべての中で最もクリーンなものではありませんが、通常のローカルオブジェクト構文を使い続けることができます。

基本的に、トリックは、オブジェクト以外に2つの追加オブジェクトをスタックに配置することです。1つはスレッドの先頭に、もう1つはコンストラクターに配置します。したがって、オブジェクトの1つはスタックafterオブジェクトに作成され、もう1つbeforeに作成されます。この情報を使用して、これら3つのオブジェクトのアドレスの順序を確認することができます。オブジェクトが実際にスタック上にある場合、そのアドレスは中央にある必要があります。

ただし、C++は関数スコープ内のオブジェクトのアドレス順序を定義しないため、次のように実行します。

int main()
{
    int a;
    int b;
    int c;
}

notは、&b&a&cの中間にあることを保証しませんか。

これを回避するには、メイン関数にaを保持し、bcを別のforce non-inlined関数:

void NO_INLINE foo()
{
    int b;
    int c;
}

int main()
{
    int a;
    foo();
}

この場合、コンパイラはfoomainのローカル変数を知ることができないため、&a> &b&c、または&a < &b&c。同じことをcに適用し、それを別のインライン化できない関数に移動することで、&bが&a&cの中間にあることを保証できます。

私の実装では、stuff関数はfoo関数であり、cを移動する関数はonly_on_stackのコンストラクターです。

実際の実装はここにあります: https://Gist.github.com/FatihBAKIR/dd125cf4f06cbf13bb4434f79e7f1d4

コンパイラが非インライン関数のローカル変数をなんらかの方法で並べ替えない限り、スタックが下向きに成長するか上向きに成長するかに関係なく、オブジェクトファイルの種類やできればABIに関係なく機能するはずです。

これは、Linuxのg ++​​-6では-O3で、mac osxでは最新のclangでテストされました。それはMSVCで動作するはずです、うまくいけば誰かがそれをテストすることができます。

両方からの出力は次のとおりです。

1: I'm NOT on stack
2: I'm on stack
3: I'm NOT on stack
4: I'm NOT on stack

使用法は基本的に、すべてのスレッドの先頭にstack_markerオブジェクト(mainを含む)を置き、別のインライン化できない関数を呼び出して、実際のエントリポイント。

6
Fatih BAKIR

次のような一時変数(寿命が延長されている)のみを許可する可能性があります。

class A
{
private:
    A() = default;
    A(const A&) = delete;
    A(A&&) = delete;
    A& operator =(const A&) = delete;
    A& operator =(A&&) = delete;

public:
    static A Make() { return {}; } 
};

auto&& g = A::Make(); // possible :/

int main() {
    auto&& a = A::Make(); // possible

#if 0
    new A(); // error

    struct InnerA
    {
        A a; // error
    };
#endif
}

コピーの省略が保証されているC++ 17では無効になります。

5
Jarod42