Инициализация большого глобального константного объекта - PullRequest
3 голосов
/ 27 ноября 2010

Как вы используете файл для инициализации глобального объекта const, который слишком велик для создания в стеке?Пока это моя попытка:

// 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();

AFAIK. Это работает в том смысле, что constLut корректно инициализируется, но есть утечка памяти, так как bigLut_t * lut никогда не освобождается.Я попытался использовать для этого умный указатель, но в результате значения в constLut были совершенно случайными.Я озадачен отсутствием информации, которую я нашел, пытаясь найти решение Google.

Ответы [ 6 ]

2 голосов
/ 27 ноября 2010

Есть несколько хороших решений вашей проблемы. Решения, предлагаемые в качестве ответов, пока не относятся к числу хороших решений (в частности, динамическое размещение, копирование и использование времени жизни временных объектов - это просто очень плохо & trade;). Я просто дам вам одно общее решение.

Простой способ предоставить огромную константу - использовать синглтон Мейерса , что означает определение константы как локальной переменной static в функции, которая возвращает ссылку на нее:

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

Это еще не полное решение, но давайте сначала посмотрим, как можно избавиться от необходимости вызывать функцию и вместо этого иметь дело непосредственно с тем, что выглядит как постоянная:

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.

В вашем случае, когда BigThing - это массив, который должен быть инициализирован из данных в файле, вы не можете напрямую полагаться на конструктор по умолчанию. Вы могли бы, если бы вы определили класс-оболочку, и это один из способов. Хорошо, давайте сделаем это (это то, что я бы выбрал, я думаю):

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.

Примечание 1. Я использовал inline, чтобы код мог быть помещен в заголовочный файл. Просто удалите для реализации, помещенный в файл реализации.

Примечание 2. Этот код не затрагивается руками компилятора и может содержать ошибки, TYPPOS и еще много чего. :-) Но эта идея - ваш ответ.

Примечание 3: Как уже упоминалось, есть и другие хорошие способы сделать это, так что это не "ответ", и нет ответа "ответ", но это "ответ".

Приветствия и hth.,

2 голосов
/ 27 ноября 2010

Как вы использовали shared_ptr?Попробуйте следующее:

// 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());
1 голос
/ 27 ноября 2010

Просто сделайте это по старинке - создайте автоматический указатель, жизненный цикл которого - весь жизненный цикл приложения, и внешнюю ссылку на указатель:

// 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() );

Нет дополнительного копирования, код клиентавидит переменную extern, как и раньше, хотя компоновщик может иметь дополнительную косвенность, а не переменную extern, находящуюся по фиксированному адресу (& constLutPtr находится в куче, а не в области статических данных).фиксированный адрес для constLut важен, вернитесь к внешнему значению, а не к внешней ссылке, прочитайте данные, используя переинтерпретацию и const_cast из & constLutPtr.передать reinterpret_cast<char*>(const_cast<bigLut_t*>(&constLut)) потоку чтения

1 голос
/ 27 ноября 2010

Не проще ли сделать глобальный объект указателем, new временным объектом из initializeConstLut и установить глобальный указатель на этот объект перед выходом из функции?

Если объектнастолько велика, что вам следует избегать любого решения, которое включает копирование и / или назначение.Таким образом, у вас есть несколько вариантов:

  • Сделать глобальный объект указателем (бесплатная копия)
  • Сделать глобальный объект неконстантным и читать непосредственно в нем
  • Не могу поверить, что говорю это, но используйте шаблон Singleton, как , и оберните объект.Это позволит вам изолировать инициализацию и предоставить открытый метод для доступа, который будет возвращать постоянную ссылку на массив.

Я не буду добавлять здесь много подробностей, потому что это действительно зависит от ваших требований (яЯ думаю о безопасности потоков, например).

0 голосов
/ 27 ноября 2010

Добавить явное уничтожение лютни. Вы несете ответственность за уничтожение всего, что вы создаете. Лучше всего сделать это явно:

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

Затем просто вызовите эту функцию перед выходом main. Вы никогда не должны полагаться на статическую инициализацию и уничтожение в C ++. Лучше всегда делать явную инициализацию и уничтожение.

0 голосов
/ 27 ноября 2010

Существует мнение, что известные объекты не протекают, если единственная утечка находится в конце срока службы приложения.Если все, что вы делаете с объектом, - это удаляете его из памяти (и не освобождаете другие ресурсы, не обновляете какие-либо внешние вещи, такие как базы данных и файлы), тогда вы можете просто оставить его там.В конце концов, явно удаляя их из памяти, вы просто блокируете приложение от завершения без какой-либо реальной выгоды: память будет освобождена тем или иным способом.

...