2010-11-27 40 views
3

Comment utiliser un fichier pour initialiser un objet const global trop grand pour être créé dans la pile? Ceci est ma tentative à ce jour:Initialisation d'un objet constant global de grande taille

// test.h 
#pragma once 
#include <boost/array.hpp> 

typedef boost::array<int,100000> bigLut_t; 
extern const bigLut_t constLut; 

// test.cpp 
#include <fstream> 
#include <boost/filesystem.hpp> 
#include "test.h" 

bigLut_t& initializeConstLut() 
{ 
    if(boost::filesystem::exists("my_binary_file") == false) { 
     std::ofstream outStream("my_binary_file", ios::out | ios::binary); 
     bigLut_t* tempLut = new bigLut_t; 
     for(int i = 0; i < 100000; ++i) { 
      // Imagine this taking a long time, 
      // which is why we're using a file in the first place 
      tempLut->at(i) = i; 
     } 
     outStream.write(reinterpret_cast<char*>(tempLut), sizeof(bigLut_t)); 
     outStream.close(); 
     delete tempLut; 
    } 
    // We can't write "bigLut_t lut;" because that would cause a stack overflow 
    bigLut_t* lut = new bigLut_t; // lut gets never deallocated 
    std::ifstream inStream("my_binary_file", ios::in | ios::binary); 
    inStream.read(reinterpret_cast<char*>(lut), sizeof(bigLut_t)); 
    inStream.close(); 
    return *lut; 
} 

const bigLut_t constLut = initializeConstLut(); 

cela fonctionne dans AFAIK un sens qui se constLut corretly initialisé, mais il y a une fuite de mémoire depuis bigLut_t * LUT ne fait jamais désallouée. J'ai essayé d'utiliser un pointeur intelligent pour cela, mais cela a abouti à des valeurs dans constLut étant tout à fait aléatoire. Je suis déconcerté par le manque d'information que j'ai trouvé en essayant de google la solution.

Répondre

2

Comment avez-vous utilisé shared_ptr? Effectuez les opérations suivantes:

// test.h 
#pragma once 
#include <boost/array.hpp> 

typedef boost::array<int,100000> bigLut_t; 
extern const bigLut_t constLut; 

// test.cpp 
#include <fstream> 
#include <boost/filesystem.hpp> 
#include "test.h" 

boost::shared_ptr<bigLut_t> initializeConstLut() 
{ 
    if(boost::filesystem::exists("my_binary_file") == false) { 
     std::ofstream outStream("my_binary_file", ios::out | ios::binary); 
     bigLut_t* tempLut = new bigLut_t; 
     for(int i = 0; i < 100000; ++i) { 
      // Imagine this taking a long time, 
      // which is why we're using a file in the first place 
      tempLut->at(i) = i; 
     } 
     outStream.write(reinterpret_cast<char*>(tempLut), sizeof(bigLut_t)); 
     outStream.close(); 
     delete tempLut; 
    } 
    // We can't write "bigLut_t lut;" because that would cause a stack overflow 
    boost::shared_ptr<bigLut_t> lut(new bigLut_t); // lut gets never deallocated 
    std::ifstream inStream("my_binary_file", ios::in | ios::binary); 
    inStream.read(reinterpret_cast<char*>(lut), sizeof(bigLut_t)); 
    inStream.close(); 
    return lut; 
} 

const bigLut_t constLut = *(initializeConstLut().get()); 
+0

Je suis sûr que j'ai essayé à la fois auto_ptr et shared_ptr, mais je suppose que cela n'a pas fonctionné parce que j'ai dé-référencé le pointeur intelligent dans la fonction initializeConstLut. Mais cela semble fonctionner parfaitement, merci. Vous avez manqué .get() à droite de lut dans la fonction de lecture. – zeroes00

+0

Je ne voudrais pas offenser un camarade parisien, mais je trouve dommage que votre solution nécessite la construction d'un objet énorme temporaire suivi d'une construction de copie (ce qui je présume est coûteux) et d'une destruction de l'objet temporaire – icecrime

+0

@icecrime : Je suis complètement d'accord, mais j'ai suivi l'exemple de zeroes00 sur celui-ci.Je n'avais aucun moyen de savoir si les choses pouvaient être faites d'une autre manière, alors j'ai répondu à la question, pas plus. –

0

Il y a une ligne de pensée qui dit que les objets connus ne fuient pas, si la seule fuite est à la fin de la vie de l'application. Si tout ce que vous faites avec l'objet est de le retirer de la mémoire (et de ne pas libérer d'autres ressources, ni de mettre à jour des éléments externes comme les DB et les fichiers), vous pouvez simplement le laisser là. Après tout, en les supprimant explicitement de la mémoire, vous bloquez simplement la fin de l'application sans réel bénéfice: la mémoire sera libérée dans un sens ou dans l'autre.

+0

Je suppose que vous ne l'avez pas remarqué que les données (qui est en tas) pointées par LUT est copié sur constLut et depuis LUT est jamais désallouée, la grande table de recherche est laissée là en le tas qui consomme de la mémoire pour rien – zeroes00

+0

Blimey, c'est vrai! Oh, eh bien ... – Dialecticus

0

Ajoutez une destruction explicite du lut. Vous êtes responsable de détruire tout ce que vous créez. Le mieux est de le faire explicitement:

void destroyLut(bigLut_t& lut) 
    { 
     delete &lut; 
    } 

Ensuite, il suffit appeler cette fonction avant de quitter main. Vous ne devriez jamais compter sur l'initialisation statique et la destruction en C++. Mieux vaut toujours faire une initialisation et une destruction explicites.

1

Ne serait-il plus facile de simplement faire de votre objet global un pointeur, new un objet temporaire initializeConstLut et définissez le pointeur global à cet objet avant de quitter la fonction ?

Si l'objet est trop grand, vous devez éviter toute solution impliquant une copie et/ou une affectation. Donc, vous vous retrouvez avec plusieurs choix:

  • Faire l'objet global un pointeur (copie)
  • Faire l'objet global non-const et lire directement dans ce
  • Je ne peux pas croire que je » Je dis cela, mais utilise un motif Singleton comme et enveloppe l'objet. Cela vous permettra d'isoler l'initialisation et de fournir une méthode publique d'accès qui retournera une référence const au tableau.

Je n'ajoute pas beaucoup de détails ici parce que cela dépend vraiment de vos besoins (je pense à la sécurité des threads par exemple).

+0

La chose la plus importante pour moi est la vitesse d'accès à ces grandes tables de consultation constante, car elles sont lues constamment et simultanément à partir de plusieurs threads. Ai-je tort de penser que, lors de l'utilisation d'un pointeur const global sur un objet const, le compilateur peut ne pas faire toutes les optimisations qu'il peut faire en utilisant un objet const global? – zeroes00

+0

@ zeroes00: Je ne suis pas un expert mais je ne vois pas pourquoi cela pourrait interférer avec les optimisations (mais vous devriez poser cette question comme une question séparée). – icecrime

1

juste le faire à l'ancienne est très bien - créer un pointeur automatique dont le cycle de vie est le cycle de vie de l'application entière, et une référence externe au pointeur:

// test.h 
#pragma once 
#include <boost/array.hpp> 

typedef boost::array<int,100000> bigLut_t; 
extern const bigLut_t& constLut; // make it a reference 

// test.cpp 
#include <fstream> 
#include <boost/filesystem.hpp> 
#include "test.h" 

namespace { 
    std::auto_ptr<bigLut_t> initializeConstLut() 
    { 
     std::auto_ptr<bigLut_t> lut(new bigLut_t); 

     if(boost::filesystem::exists("my_binary_file") == false) { 
      std::ofstream outStream("my_binary_file", ios::out | ios::binary); 

      for(int i = 0; i < 100000; ++i) { 
       // Imagine this taking a long time, 
       // which is why we're using a file in the first place 
       lut->at(i) = i; 
      } 

      outStream.write(reinterpret_cast<char*>(lut), sizeof(bigLut_t)); 
      outStream.close(); 

      // no point writing then reading the same data 
     } else {    
      std::ifstream inStream("my_binary_file", ios::in | ios::binary); 
      inStream.read(reinterpret_cast<char*>(lut.get()), sizeof(bigLut_t)); 
      inStream.close(); 
     } 

     return lut; 
    } 

    // local to this compilation unit, deletes object on exit 
    std::auto_ptr<bigLut_t> constLutPtr (initializeConstLut()); 
} 

// the extern reference refers to the object held by the auto_ptr 
const bigLut_t& constLut (*constLutPtr.get()); 

Pas de copie supplémentaire, code client voit la variable externe comme précédemment, bien que le linker puisse avoir une indirection supplémentaire plutôt que la variable externe étant dans une adresse fixe (& constLutPtr est sur le tas plutôt que dans la zone de données statiques).

Si avoir une adresse fixe pour constLut est important, revenez à avoir une valeur externe plutôt qu'une référence externe, lisez les données en utilisant une distribution de réinterprétation et une const_cast de & constLutPtr. passez reinterpret_cast<char*>(const_cast<bigLut_t*>(&constLut)) au flux lu.

+0

Au début, j'ai essayé de faire cette dernière suggestion (en écrivant sur l'objet const global en utilisant const_cast) mais chaque fois que j'ai eu une erreur d'exécution disant "Access violation writing location ...". Mais maintenant que je l'essaie, ça marche! Peut-être que j'ai fait quelque chose différemment ou peut-être que je suis juste vraiment chanceux/malchanceux maintenant. Je pense que je dois créer une autre question à ce sujet. – zeroes00

2

Il existe plusieurs bonnes solutions à votre problème. Les solutions proposées comme réponses ne sont pas parmi les bonnes solutions (en particulier, l'allocation dynamique et la copie et en s'appuyant sur la durée de vie des objets temporaires est juste très mauvais ™). Je vais juste vous donner une solution commune.

Une façon simple de fournir une grande constante est d'utiliser un Meyers de singleton, ce qui signifie définir la constante comme une variable locale static dans une fonction qui renvoie une référence à elle:

inline BigThing const& theBigThing() 
{ 
    static BigThing const theInstance; // Default constructor does the init job. 
    return theInstance; 
} 

Cette est pas encore une solution complète, mais nous allons d'abord voir comment vous pouvez vous débarrasser d'avoir à invoquer une fonction, et de traiter au lieu directement avec ce que-look-like-a-constante:

namespace detail { 
    inline BigThing const& theBigThing() 
    { 
     static BigThing const theInstance; // Default constructor does the init job. 
     return theInstance; 
    } 
} 

BigThing const& theBigThing = detail::theBigThing(); // No copying, just ref. 

Dans votre cas où BigThing est un tableau qui doit être initialisé à partir des données d'un fichier, vous ne pouvez pas compter directement sur le constructeur par défaut. Vous pourriez si vous avez défini une classe wrapper, et c'est un moyen. OK, nous allons faire cela (c'est ce que je choisirais, je pense):

namespace detail { 

    struct BigThingWrapper 
    { 
     BigThing thingy_; 

     BigThingWrapper() 
     { 
      // Initialize the thingy_ member here. 
     } 
    }; 

    inline BigThing const& theBigThing() 
    { 
     static BigThingWrapper const theInstance; 
     return theInstance.thingy_; 
    } 
} 

BigThing const& theBigThing = detail::theBigThing(); // No copying, just ref. 

Note 1: Je l'ai utilisé inline afin que le code pourrait en théorie être placé dans un fichier d'en-tête. Supprimez simplement l'implémentation placée dans un fichier d'implémentation.

Remarque 2: Ce code n'est pas touché par les mains du compilateur et peut donc contenir ERORS, TYPPOS et autres commandes. :-) Mais c'est l'idée qui est ta réponse.

Note 3: Il y a, comme mentionné, d'autres bonnes manières de le faire, donc ce n'est pas "la" réponse, et il n'y a pas de "la" réponse, mais c'est "une" réponse.

Vive & HTH.,