Non-deferred initialization of local static objects?

335 views Asked by At

Is there any pattern or other nonstandard mechanism for either gcc (4.8) or icc (14.0) that can guarantee the early, safe construction of static locals?

I need a global collection of local static objects references for the purposes of coarse profiling controllable at run-time. I am actively hurt by standard deferred construction (as well as by dealing with locked or redundant thread_local collections), and it would be highly advantageous to have complete point lists at start time.

Any hope to achieve this?

#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

I've read up on Schwarz Counters and sections 3.6.2 (basic.start.init) / 6.7 (stmt.decl) of the C++11 spec, but I don't have as much knowledge about compiler-specific behavior and haven't been able to find anyone else posting about trying to achieve this trick.

Accepted answer:

As John notes below, all classes (may) have their static members initialized before main(), but given that C++11 §9.4.2/5 [class.static.data] and §9.8/4 [class.local] forbid static data members in local classes, a class that is templated over a local class and has a static data member of that class can have its initialization done at start-time. Quite a brilliant insight, and even more subtle than I first thought!

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

Note also that using a Meyers singleton method for the collection completes the overall safety of this approach. The collection may have to be locked to guard against concurrent static initializations of the points, however. I still need to check spec to confirm the specification for this and whether the static member initialization is actually forced to be done before main().

2

There are 2 answers

7
John Bandela On BEST ANSWER

Try this

#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

This works for MSVC 2013 and ideone http://ideone.com/Z3n1U0

This does require use of macro and to call doStuff() you have to do .p.doStuff(). You also cannot have more than 1 profile point in a function (but this can easily be fixed).

This works by defining a local class that is used as a parameter to a template class that has a static member. By referencing that template in the function, you force the compiler to instantiate the static member of the template.

Let me know if you have any questions about this technique.

0
AudioBubble On

You might do it like:

#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;
}

This maintains a single instance of a profile point container in a function and initialize each profile point during translation unit initialization.