【问题标题】:Initializing a large global constant object初始化一个大的全局常量对象
【发布时间】:2010-11-27 08:21:35
【问题描述】:

如何使用文件来初始化一个太大而无法在堆栈中创建的全局 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 中的值非常随机。通过尝试谷歌解决方案,我发现缺乏信息,这让我感到困惑。

【问题讨论】:

    标签: c++ initialization constants global


    【解决方案1】:

    你是如何使用 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());
    

    【讨论】:

    • 我很确定我确实尝试了 auto_ptr 和 shared_ptr,但我猜它不起作用,因为我在 initializeConstLut 函数中取消了智能指针的引用。但这似乎完美无缺,谢谢。但是,您在读取函数中错过了 lut 右侧的 .get()。
    • 我不想冒犯巴黎同胞,但我觉得很遗憾您的解决方案需要构建一个临时的巨大对象,然后是一个复制构建(我认为这很昂贵)和一个临时销毁
    • @icecrime :我完全同意,但我在这件事上遵循了 zeroes00 的领导。我无法知道事情是否可以通过其他方式完成,所以我回答了这个问题,不再赘述。
    【解决方案2】:

    有几个很好的解决方案可以解决您的问题。到目前为止,作为答案提供的解决方案并不是很好的解决方案(特别是,动态分配和复制以及依赖临时对象的生命周期只是非常糟糕™)。我只会给你一个常见的解决方案。

    提供大常量的一种简单方法是使用Meyers 的单例,这意味着将常量定义为函数中的static 局部变量,返回对其的引用: p>

    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:此代码未经过编译器的处理,因此可能包含 ERORS、TYPPOS 等。 :-) 但这是你的答案。

    注意 3:如前所述,还有其他好的方法可以做到这一点,所以这不是“the”答案,也没有“the”答案,而是“an”答案。

    干杯,

    【讨论】:

      【解决方案3】:

      只用老式的方式就可以了——创建一个自动指针,其生命周期是整个应用程序生命周期,以及对指针的外部引用:

      // 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 的固定地址很重要,请返回使用 extern 值而不是 extern 引用,使用重新解释转换和 &constLutPtr 的 const_cast 读取数据。将reinterpret_cast&lt;char*&gt;(const_cast&lt;bigLut_t*&gt;(&amp;constLut)) 传递给读取的流。

      【讨论】:

      • 起初我尝试执行你的最后一个建议(使用 const_cast 覆盖全局 const 对象),但每次我收到运行时错误,提示“访问冲突写入位置 ...”。但现在我尝试了,它有效!也许我做了不同的事情,或者我现在真的很幸运/不幸。我想我必须对此提出另一个问题。
      【解决方案4】:

      让你的全局对象成为一个指针,new 一个来自initializeConstLut 的临时对象并在退出函数之前将全局指针设置为该对象不是更容易吗?

      如果对象太大,您应该避免任何涉及复制和/或分配的解决方案。所以你有几个选择:

      • 将全局对象设为指针(免费副本)
      • 将全局对象设为非 const 并直接在其中读取
      • 我不敢相信我会这么说,但是使用 Singleton like 模式并包装对象。这将允许您隔离初始化并提供一个公共访问方法,该方法将返回对数组的 const 引用。

      我不会在这里添加太多细节,因为这实际上取决于您的要求(例如,我正在考虑线程安全)。

      【讨论】:

      • 对我来说最重要的是访问这些大型常量查找表的速度,因为它们是从多个线程中不断地并发地读取的。那么我是否错误地认为,当使用指向 const 对象的全局 const 指针时,编译器可能无法完成使用全局 const 对象时可以做的所有优化?
      • @zeroes00:我不是专家,但我不明白为什么这会干扰优化(但您应该将其作为一个单独的问题提出)。
      【解决方案5】:

      如果唯一的泄漏是在应用程序生命周期结束时,有一种思路认为已知对象不会泄漏。如果您对对象所做的只是将其从内存中删除(并且不释放其他资源,也不更新数据库和文件等一些外部内容),那么您可以将其留在那里。毕竟,通过从内存中显式删除它们,您只是阻止应用程序完成而没有真正的好处:内存将以一种或另一种方式释放。

      【讨论】:

      • 我猜你没有注意到 lut 指向的数据(在堆中)被复制到 constLut 上,并且由于 lut 永远不会被释放,大查找表留在堆消耗内存中不劳而获
      【解决方案6】:

      添加对 lut 的显式销毁。你有责任摧毁你创造的任何东西。最好是明确地做到这一点:

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

      然后在退出main 之前调用这个函数。您永远不应该依赖 C++ 中的静态初始化和销毁​​。更好的是始终进行显式初始化和销毁​​。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-12-19
        • 1970-01-01
        • 2015-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多