31 votes

Existe-t-il un moyen d'imposer que les instances ne soient jamais sur la pile?

J'ai une classe C++ pour laquelle je ne veux plus jamais être instancié sur la pile. Je suis en utilisant une api pour accéder au contenu qui a été développé dans un autre (interprété) de la langue qui vient avec sa propre collecte des ordures. Les mécanismes de cette langue en savons assez pour laisser tout contenu qu'il trouve des références à sur la pile seuls, et, depuis ce natif classe contient une telle référence, il est d'une importance vitale pour le bon comportement, l'utilisateur de la maternelle à la classe C++ il ne jamais essayer de les attribuer à une instance de n'importe où ailleurs.

Remarque, je ne veux pas seulement d'interdire l'instance de ma classe allouée à nouvelles (si c'était tout ce que j'avais à faire, je pouvais à la surcharge de la classe new de l'opérateur et de le rendre privé, ou explicitement le supprimer depuis C++11), mais aussi d'interdire toute statique ou global possible des instances de la classe ainsi. Le seul moyen valable pour instancier cette classe en toute sécurité devrait être mis sur la pile, et je voudrais en quelque sorte les garants. Autant que je sache, rendant new privé ou de le supprimer également ne pas empêcher une autre classe d'être déclarée avec ma classe comme une variable de membre et d'une instance de l'être alloué sur le tas.

Comment je gère en ce moment est d'avoir le mot "Local" comme partie du nom de la classe comme un rappel amical de la part de l'utilisateur que l'instance est uniquement destiné à être utilisé sur la pile, mais bien sûr, ce n'est pas réellement appliqué par le compilateur ou de tout autre mécanisme, et je préfère une solution qui est plus exécutoire.

Idéalement, je veux m'assurer que ce au moment de la compilation et de l'échec de la compilation si elle est utilisée de manière incorrecte. Si ce n'est tout simplement pas possible, en levant une exception lors de l'exécution lorsque l'instance est construit est encore acceptable pour une solution de repli. Des Solutions qui fonctionnent dans C++11 et C++14 sont beaux.

Veuillez noter que cette question n'est certainement PAS la même chose que ce , qui ne voulait empêcher allocaton avec new

40voto

lorro Points 1220

Avertissement: une "pile" ne fait pas partie de la norme c++, à ma connaissance, là, nous avons ASDVs (automatique de la durée de stockage des variables). ABI pourrait définir la pile. Notez que parfois, ceux-ci sont passés dans les registres, je crois que c'est OK dans votre cas.

Définir un PC (continuation passing style) de l'usine méthode:

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;
};

Utilisation: passer un lambda de prendre Un et de le ctor paramètres de l'A. E. g.

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

Les arguments de la fonction sont toujours ASDVs à l'intérieur.

Comment fonctionne le code: cps_make prend un foncteur (généralement un lambda) qui prend une instance d'un type donné; et en option ctor paramètres. Il crée l'instance (par l'envoi d'un facultatif params à la ctor), appelle le foncteur et renvoie ce que le foncteur retourne. Depuis le foncteur peut être un lambda en C++11, il ne rompt pas le code de flux.

La beauté de la CPS est, vous pouvez avoir polymorphisme statique seulement par l'utilisation d'un auto-lambda en C++14: votre cps_make() permet de créer tout ce que vous souhaitez (hiérarchie, variante, tout, etc.). Ensuite, vous enregistrez le virtuel surcharge fermé hiérarchies. Vous pouvez même avoir un lambda pour les flux normal et un si ctor serait un échec; c'est pratique lorsque les exceptions sont des no-go.

L'inconvénient est, actuellement, vous ne pouvez pas utiliser directement le flux de contrôle des états de l'extérieur de la portée à l'intérieur de la lambda. /* Indice: nous sommes en train de travailler sur elle. */

6voto

Fatih BAKIR Points 1562

Bon, alors voici mon point de vue:

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
}

Certes, ma solution n'est pas le plus propre de tous, mais il vous permet de continuer à utiliser le local objet de la syntaxe.

En gros, le truc, c'est de mettre 2 autres objets sur la pile, d'autres que notre objet: l'une au début du fil, et un constructeur. Par conséquent, l'un des objets est créé sur la pile après notre objet et l'un d'eux avant. Avec cette information, nous pourrions nous contenter de vérifier l'ordre des adresses de ces 3 objets. Si l'objet est vraiment sur la pile, l'adresse de l', il devrait être dans le milieu.

Cependant, le C++ n'est pas de définir l'adresse de l'ordre des objets dans un domaine de la fonction, donc faire quelque chose comme ceci:

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

Ne pas garantir qu' &b est dans le milieu de l' &a et &c.

Pour contourner ce problème, nous avons pu continuer à a dans la fonction principale et déplacer b et c dans une autre force de la non-inline fonction:

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

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

Dans ce cas, puisque le compilateur ne peut pas savoir les variables locales foo en main, &a > &b, &c ou &a < &b, &c. En appliquant la même chose pour c en le déplaçant vers un autre non-inlineable fonction, nous pourrions garantir que &b est le milieu de l' &a et &c.

Dans mon implémentation, stuff fonction est l' foo fonction et la fonction qui nous déplacer c dans le constructeur est de only_on_stack.

De travail effectif de la mise en œuvre est ici: https://gist.github.com/FatihBAKIR/dd125cf4f06cbf13bb4434f79e7f1d43

Il devrait fonctionner si la pile grandit vers le bas ou vers le haut et indépendamment de l'objet type de fichier et j'espère que les ABI, aussi longtemps que le compilateur n'a pas en quelque sorte réorganiser les variables locales de la non-fonctions inline.

Cela a été testé avec -O3 g++-6 sur linux et les dernières clang sur mac os x. Il doit travailler sur MSVC, j'espère que quelqu'un peut le tester.

Sortie des deux est:

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

L'utilisation est en fait, vous mettez un stack_marker objet au début de chaque thread (main inclus) et appeler un autre pas inlineable fonction et l'utiliser comme point d'entrée réel.

5voto

Jarod42 Points 15729

Une possibilité consiste à n'autoriser que des variables temporaires (avec une durée de vie prolongée), par exemple:

 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
}
 

Il ne sera plus valable en C ++ 17 avec une élision de copie garantie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X