2013-12-15 2 views
10

Имеет ли gcc какие-либо гарантии относительно статического времени инициализации члена, особенно в отношении классов шаблонов?Инициализация статического члена без отсрочки для шаблонов в gcc?

Я хочу знать, могу ли я получить твердую гарантию, что статические члены (PWrap_T<T>::p_s) будут инициализированы до main(), когда классы создаются через несколько единиц компиляции. Нецелесообразно пытаться вручную коснуться символа из каждой единицы компиляции в начале основного, но мне не ясно, что что-нибудь еще будет работать.

Я проверил с методами, как bar() в различных единицах измерения и всегда получили желаемый результат, но мне нужно знать, когда/если когда-либо НКУ будет дергать ковер, и является ли это предотвратить.

Кроме того, будут ли инициализированы все статические элементы от DSO до того, как библиотека завершит загрузку?

#include <iostream> 
#include <deque> 

struct P; 
inline std::deque<P *> &ps() { static std::deque<P *> d; return d; } 
void dump(); 

struct P { 
    P(int id, char const *i) : id_(id), inf_(i) { ps().push_back(this); } 
    void doStuff() { std::cout << id_ << " (" << inf_ << ")" << std::endl; } 
    int const  id_; 
    char const *const inf_; 
}; 

template <class T> 
struct PWrap_T { static P p_s; }; 

// *** Can I guarantee this is done before main()? *** 
template <class T> 
P PWrap_T<T>::p_s(T::id(), T::desc()); 

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

// In a compilation unit apart from the template/macro header. 
void dump() { 
    std::cout << "["; 
    for (P *pp : ps()) { std::cout << " " << pp->id_ << ":" << pp->inf_; } 
    std::cout << " ]" << std::endl; 
} 

// In some compilation unit. 
void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s.doStuff(); 
    } 
} 

int main() { 
    dump(); 
    PP(3, "another", pp2); 
    bar(5); 
    pp2.p_s.doStuff(); 
} 

(C++ 11 §3.6.2/4 - [basic.start.init] :)

Она определяется реализацией ли динамическая инициализация не локальное переменная со статической продолжительностью хранения выполняется до первого утверждения main. Если инициализация отложена до некоторого момента времени после первого утверждения main, она должна произойти до первого использования odr (3.2) любой функции или переменной, определенной в той же самой единицы перевода, что и переменная, подлежащая инициализации.

... Нелинейная переменная со статической продолжительностью хранения, имеющей инициализацию с побочными эффектами, должна быть инициализирована, даже если она не используется odr (3.2, 3.7.1).

Кроме того, пытаясь __attribute__ ((init_priority(int))) или __attribute__ ((constructor)) для инициализации члена шаблона дали warning: attributes after parenthesized initializer ignored, и я не знаю никаких других уловок относительно статической инициализации.

Заранее благодарим всех, кто может дать мне ответ об этом!

+0

я себе 'правило ODR-use' предназначен для покрытия динамических разделяемых объектов (DSO,), которые могут иметь объекты файловой области видимости. Вы, очевидно, не можете инициализировать все в DSO, если он появился после основных запусков 'dlopen()', но теоретически 'dlopen()' может гарантировать, что все в DSO инициализируется, прежде чем вы вызовете что-нибудь еще в DSO. Я полагаю, что ответ в конечном итоге определяется ABI для любой OS/архитектуры, которую вы компилируете. –

+0

Одноэлементный шаблон решает проблему, не так ли? – lkanab

ответ

2

Стандарт гарантирует, что объекты продолжительности статического хранения инициализируются до того, как любые функции/переменные в той же самой системе переводов, что и ваш объект, будут использованы из внешнего источника.

Формулировка здесь предназначена для работы с разделяемыми библиотеками. Поскольку разделяемые библиотеки могут быть динамически загружены после запуска main(), спецификация языка должна быть достаточно гибкой, чтобы справляться с ней. Но пока вы обращаетесь к своему объекту из-за пределов единицы перевода, вам гарантируется, что он будет создан до того, как вы получите доступ (если вы не делаете что-то патологическое).

BUT Это не останавливает его до инициализации, если оно используется в конструкторе другого объекта продолжительности статического хранения в том же компиляторе.

Но вы можете легко вручную обеспечить гарантии того, что статический объект будет правильно инициализирован до использования с использованием нижеприведенной техники.

Просто измените статическую переменную на статическую. Затем внутри функции объявляется статический член, который возвращается. Таким образом, вы можете использовать его точно так же, как и раньше (просто добавьте ()).

template <class T> 
struct PWrap_T 
{ 
    static P& p_s(); // change static variable to static member function. 
         // Rather than the type being P make it a reference to P 
         // because the object will be held internally to the function 
}; 

template <class T> 
P& PWrap_T<T>::p_s() 
{ 
    // Notice the member is static. 
    // This means it will live longer than the function. 
    // Also it will be initialized on first use. 
    // and destructed when other static storage duration objects are destroyed. 
    static P p_s_item(T::id(), T::desc()); 

    return p_s_item; 

    // Note its not guaranteed to be created before main(). 
    // But it is guaranteed to be created before first use. 
} 

Здесь вы получаете преимущества глобального (независимо от того, что они есть). Вы получаете гарантированное строительство/разрушение, и вы знаете, что объект будет правильно построен до его использования.

Единственное изменение, которое нужно сделать это:

void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s().doStuff(); 
    // ^^ Add the braces here. 
    } 
} 
+0

Только проблема заключается в том, что функция-статика не является потокобезопасной (инициализация их, по крайней мере) в pre-C++ 11 iirc. – Bwmat

+0

@Bwmat: Истинный язык не гарантирует его до C++ 11. Но gcc делает (даже pre C++ 11). –

+1

@Bwmat: http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html –

1

Как вы уже узнали, стандарт C++ не гарантирует, что «динамическая инициализация нелокальной переменной со статической продолжительностью хранения будет выполнена до первого утверждения main». Однако GCC выполняет такую ​​инициализацию перед выполнением main, как описано в How Initialization Functions Are Handled.

Единственная проблема заключается в инициализации статических объектов из разделяемых библиотек, загружаемых dlopen. Они будут инициализироваться только во время загрузки библиотеки, но вы ничего не можете с этим поделать.