2015-10-03 2 views
3

J'ai besoin d'implémenter un conteneur pour contenir une quantité d'éléments et pour une raison quelconque, il doit fonctionner sans allocation de tas. Une autre exigence est que les éléments du conteneur ne doivent pas être copiés ou déplacés de quelque manière que ce soit. Ils doivent être construits directement dans la mémoire allouée par le conteneur. Pour cela, j'ai décidé d'utiliser le placement new et de déléguer complètement la gestion de la mémoire à l'implémentation du conteneur (j'ai trouvé quelques informations utiles sur le placement new à drdobbs).Comment implémenter un conteneur simple avec des fonctionnalités de placement et de mise en place?

Un exemple courant est trouvé here. (S'il vous plaît noter que l'utilisation de new uint8_t[size] et std::queue est juste pour garder l'exemple simple. Vrai code Ma a plus complexe, l'implémentation moins tas à la place.)

Cela fonctionne parfaitement jusqu'à présent, comme le code client doit mettre des éléments dans le conteneur avec des appels tels que:

executer.push(new (executer) MyRunnable("Hello", 123)); 

maintenant, je veux ne pas retirer la nécessité de l'écriture répétée executer dans cette déclaration. Je voudrais plutôt écrire quelque chose comme .: par exemple

executer.pushNew(MyRunnable("Hello", 123)); 

ou

executer.pushNew(MyRunnable, "Hello", 123); 

peut-être en fournissant un modèle approprié, mais je ne ai pas écrire un (pas de macros préprocesseur, s'il vous plaît).

J'ai trouvé quelques informations utiles sur std::allocator ici à drdobbs mais je ne sais pas comment l'appliquer à mon problème (en outre, l'article est d'anno 2000 et donc ne pas utiliser le C++ 11 possible avantages).

Pourrait-on m'aider à trouver un moyen de ne plus avoir besoin de donner le executer deux fois?

Edit: Après avoir réussi l'approbation réponse de Jarod42, je mis à jour mon exemple de code en cours d'exécution here.

Et pour l'histoire, voici le code exemple original de ma question initiale:

#include <iostream> 
#include <queue> 


class Runnable { 
    // Runnable should be uncopyable and also unmovable 
    Runnable(const Runnable&) = delete; 
    Runnable& operator = (const Runnable&) = delete;  
    Runnable(const Runnable&&) = delete; 
    Runnable& operator = (const Runnable&&) = delete;  
public: 
    explicit Runnable() {} 
    virtual ~Runnable() {} 
    virtual void run() = 0; 
}; 


class MyRunnable: public Runnable { 
public: 
    explicit MyRunnable(const char* name, int num): name(name), num(num) {} 
    virtual void run() override { 
     std::cout << name << " " << num << std::endl; 
    } 
private: 
    const char* name; 
    int num; 
}; 


class Executer { 
    // Executer should be uncopyable and also unmovable 
    Executer(const Executer&) = delete; 
    Executer& operator = (const Executer&) = delete;  
    Executer(const Executer&&) = delete; 
    Executer& operator = (const Executer&&) = delete;  
public: 
    explicit Executer() {  
    } 

    void* allocateEntry(size_t size) { 
     // this heap allocation is just to keep this example simple 
     // my real implementation uses it's own memory management instead (blockpool) 
     return new uint8_t[size]; 
    } 

    void push(Runnable* entry) { 
     queue.push(entry); 
    } 

    template <typename R> // this don't works 
    void pushNew(R) { 
     push(new (*this) R); 
    } 

    inline friend void* operator new(size_t n, Executer& executer) { 
     return executer.allocateEntry(n); 
    } 

    void execute() { 
     while (queue.size() > 0) { 
      Runnable* entry = queue.front(); 
      queue.pop(); 
      entry->run(); 
      // Now doing "placement delete" 
      entry->~Runnable(); 
      uint8_t* p = reinterpret_cast<uint8_t*>(entry); 
      delete[] p; 
     } 

    } 

private: 
    // this use of std::queue is just to keep this example simple 
    // my real implementation uses it's own heap-less queue instead 
    std::queue<Runnable*> queue {}; 
}; 


int main() { 
    Executer executer; 
    executer.push(new (executer) MyRunnable("First", 1)); 
    executer.push(new (executer) MyRunnable("Second", 2)); 
    executer.push(new (executer) MyRunnable("Third", 3)); 

    // but want to use it more like one this 
    //executer.pushNew(MyRunnable("Fifth", 5)); // how to implement it? 
    //executer.pushNew(MyRunnable, "Sixth", 6); // or maybe for this usage? 

    executer.execute(); 
} 
+0

Le nombre d'éléments est-il connu au moment de la compilation? Pourriez-vous utiliser 'std :: array'? –

+1

Non, 'std :: array' ne peut pas être utilisé. En outre, la façon dont le conteneur gère la mémoire doit être complètement privée. – Joe

+0

Avez-vous regardé l'implémentation de std :: vectors d'emplace? Cela ressemble exactement à ce dont vous avez besoin (en ignorant le problème haep vs stack, qui est un problème de sepat) – MikeMB

Répondre

8

Avec:

template <typename R, typename... Ts> 
void pushNew(Ts&&... args) { 
    push(new (*this) R(std::forward<Ts>(args)...)); 
} 

Vous pouvez écrire:

executor.PushNew<MyRunnable>("Hello", 123); 

au lieu de

executer.push(new (executer) MyRunnable("Hello", 123)); 
7

Il y a deux choses qui clochent: ce

template <typename R> // this don't works 
void pushNew(R) { 
    push(new (*this) R); 
} 

La première est répondues par Jarod42 en ce que vous voulez faire:

template <typename R, typename... Ts> 
void pushNew(Ts&&... args) { 
    push(new (*this) R(std::forward<Ts>(args)...)); 
} 

mais plus important encore ...new (*this) R est vraiment bizarre. Il semble que vous construisez un R sur vous-même! Mais vous n'êtes pas, vous utilisez simplement cette syntaxe pour appeler votre allocateur. Cela viole horriblement le principe de la moindre surprise. Il m'a fallu du temps pour comprendre ce qui se passait.

Ce que vous devez utiliser à est juste votre allocateur directement:

template <typename R, typename... Ts> 
void pushNew(Ts&&... args) { 
    void* slot = allocateEntry(sizeof(R)); 
    push(new (slot) R(std::forward<Ts>(args)...)); 
} 

C'est beaucoup plus facile à comprendre.

+0

devrait être accepté. c'est la réponse que je préfère! :) – GameDeveloper