【问题标题】:Non-deferred initialization of local static objects?本地静态对象的非延迟初始化?
【发布时间】:2013-12-11 17:52:01
【问题描述】:

对于 gcc (4.8) 或 icc (14.0) 是否有任何模式或其他非标准机制可以保证静态局部变量的早期安全构造?

我需要一个本地静态对象引用的全局集合,以便在运行时进行可控制的粗略分析。标准的延迟构造(以及处理锁定或冗余的 thread_local 集合)使我深受伤害,在开始时拥有完整的点列表将非常有利。

有希望实现这一目标吗?

#include <iostream>
#include <deque>

// Really want to build this list before main() started!
struct ProfilePoint;
static std::deque<ProfilePoint *> pps;

// Costly construction, but only ever with literal/constexpr params.
// Templating, etc., also discourages non-local building in reality.
struct ProfilePoint {
  ProfilePoint(int id, char const *i) : id_(id), inf_(i) { pps.push_back(this); }
  void doStuff() { /* ... */ }
  int id_;
  char const *const inf_;
};

// Functions like this will be called concurrently in reality.
void bar(int cnt) {
  for (int i = 0; i < cnt; ++i) {
    // Dropping in a local definition/call should be enough to hook in to system
    static ProfilePoint pp(2, "description in a string literal");
    pp.doStuff();
    /* ... */
  }
}

void dump() {
  std::cout << "[";
  for (ProfilePoint *pp: pps) { std::cout << " " << pp->id_ << ":" << pp->inf_; }
  std::cout << " ]" << std::endl;
}

int main() { dump(); bar(5); dump(); } // "[ ]" then "[ 2 ]" in gcc/icc

我已经阅读了 C++11 规范的 Schwarz Counters 和第 3.6.2 (basic.start.init) / 6.7 (stmt.decl) 部分,但我对编译器了解不多 -特定的行为,并且找不到其他人发布有关尝试实现此技巧的信息。

接受的答案:

正如 John 在下面指出的,所有类(可能) 在 main() 之前初始化其静态成员,但鉴于 C++11 §9.4.2/5 [class.static.data]§9.8/4 [class.local] 禁止静态数据本地类中的成员,在本地类上模板化并具有该类的静态数据成员的类可以在开始时完成其初始化。非常出色的洞察力,甚至比我最初想象的还要微妙!

// John Bandela's solutions (slightly condensed):
template <class TPPDesc> struct PPWrapper_T { static ProfilePoint p; };
template <class TPPDesc>
ProfilePoint PPWrapper_T<TPPDesc>::p(TPPDesc::id(), TPPDesc::desc());

#define PROFILE_POINT(ID, DESC, NAME)          \
struct ppdef_##NAME  {                         \
  static int         id()   { return ID; }     \
  static char const *desc() { return DESC; }   \
};                                             \
static PPWrapper_T<ppdef_##NAME> NAME           // semicolon must follow!

// ...

void foo() {
  PROFILE_POINT(2, "another_description", pp);
  pp.p.doStuff();
}

另请注意,对集合使用 Meyers 单例方法完成了这种方法的整体安全性。但是,可能必须锁定集合以防止点的并发静态初始化。我仍然需要检查规范以确认此规范以及静态成员初始化是否实际上强制在 main() 之前完成。

【问题讨论】:

    标签: c++ static static-initialization


    【解决方案1】:

    试试这个

    #include <iostream>
    #include <deque>
    
    // Really want to build this list before main() started!
    struct ProfilePoint;
    static std::deque<ProfilePoint *> pps;
    
    // Costly construction, but only ever with literal/constexpr params.
    // Templating, etc., also discourages non-local building in reality.
    struct ProfilePoint {
      ProfilePoint(int id, char const *i) : id_(id), inf_(i) { pps.push_back(this); }
      void doStuff() { /* ... */ }
      int id_;
      char const *const inf_;
    };
    
    template<class IdDescription>
    struct ProfilePoint_{
      static ProfilePoint p;
    
    
    };
    
    template<class IdDescription>
    ProfilePoint ProfilePoint_<IdDescription>::p( IdDescription::id(),    IdDescription::description() );
    
    #define PROFILE_POINT(theid,thedescription) \
    struct ppdef_static_class{ \
      static int id(){ return theid; } \
      static const char* description(){ return thedescription; } \
      };\
      static ProfilePoint_<ppdef_static_class>
    
    // Functions like this will be called concurrently in reality.
    void bar(int cnt) {
      for (int i = 0; i < cnt; ++i) {
        // Dropping in a local definition/call should be enough to hook in to system
        PROFILE_POINT(2, "description is a string literal") pp;
    
        pp.p.doStuff();
        /* ... */
     }
    }
    
    void dump() {
      std::cout << "[";
      for (ProfilePoint *pp : pps) { std::cout << " " << pp->id_ << ":" << pp->inf_; }
      std::cout << " ]" << std::endl;
    }
    
    int main() { dump(); bar(5); dump(); } // Does what you want
    

    这适用于 MSVC 2013 和 ideone http://ideone.com/Z3n1U0

    这确实需要使用宏并且调用 doStuff() 你必须执行 .p.doStuff()。一个函数中的配置文件点也不能超过 1 个(但这很容易解决)。

    这通过定义一个本地类来工作,该类用作具有静态成员的模板类的参数。通过在函数中引用该模板,您可以强制编译器实例化模板的静态成员。

    如果您对此技术有任何疑问,请告诉我。

    【讨论】:

    • 这也适用于我的编译器,但我不知道为什么。我有什么保证我的静态 deque 将首先从一个可能不同的编译单元初始化,是什么使模板实例化足够特殊以强制在那里进行静态初始化?感谢您提出这种方法!
    • @Jeff 无法保证 pps。要获得保证,请使用 Meyers 单例技巧 std::deque&lt;ProfilePoint *&gt;&amp; pps(){static std::deque&lt;ProfilePoint *&gt; d; return d;} 并将 pps. 替换为 pps().
    • @Jeff 至于它为什么起作用,在函数中,您正在定义一个具有静态成员的类。 C++ 标准保证类静态成员在启动时被初始化,不像函数静态变量第一次通过函数初始化。
    • 哦,呵呵。我只是假设本地类遵循本地静态对象初始化规则,并且模板实例化是相关部分。鉴于此,您在上面暗示的简单 1 pp/范围限制是多少?将实例名称传递给宏以构建唯一的 ppdef_static_class_XXX 名称?
    • @Jeff 只要 id 在函数中是唯一的,只需将 id 名称附加到宏中的类中即可。有点像ppdef_static_class ## theid
    【解决方案2】:

    你可以这样做:

    #include <iostream>
    #include <deque>
    #include <memory>
    #include <map>
    
    class ProfilePoint
    {
        public:
        typedef unsigned Identifier;
    
        private:
        struct Data {
            Identifier id;
            const char* information;
            unsigned count;
    
            Data(Identifier id, const char* information)
            :   id(id), information(information), count(0)
            {}
        };
    
        public:
        static void dump();
    
        const char* information() const { return m_data.information; }
        Identifier id() const { return m_data.id; }
    
        ProfilePoint(const char* information)
        :   m_data(*get_data(0, information))
        {}
    
        void apply() const {
            ++m_data.count;
        }
    
        private:
        static Data* get_data(Identifier, const char* information);
        Data& m_data;
    };
    
    
    ProfilePoint::Data* ProfilePoint::get_data(Identifier id, const char* information) {
        typedef std::deque<Data> StaticData;
        StaticData static_data;
        if( ! information) return &static_data[id];
        else {
            static_data.push_back(Data(static_data.size(), information));
            for(auto d: static_data)
                std::cout << d.information << std::endl;
            return &static_data.back();
        }
        return 0;
    }
    
    void ProfilePoint::dump() {
        std::cout << "dump" << std::endl;
        Data* data;
        for(Identifier i = 0; (data = get_data(i, 0)); ++i) {
            std::cout
                << "Profile Point: " << data->information
                << ", Count: " << data->count << std::endl;
        }
    }
    
    
    namespace {
    
    ProfilePoint pf("Function");
    void f() {
        pf.apply();
        pf.apply();
        pf.apply();
        ProfilePoint::dump();
    
    }
    
    } // namespace
    
    int main()
    {
        f();
        return 0;
    }
    

    这会在函数中维护配置文件点容器的单个实例,并在翻译单元初始化期间初始化每个配置文件点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-17
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多