2014-12-02 2 views
1

Я заметил странное поведение при попытке скомпилировать приведенный ниже код. У меня есть 4 файла следующим образомОшибка компилятора или правильное поведение для статической константной переменной-члена, вариативных шаблонов и &&?

createshared.h:

#ifndef CREATESHARED_H_ 
#define CREATESHARED_H_ 

#include <memory> 
#include <utility> 

#ifdef USE_REFREF 
template<typename T, typename... Args> 
std::shared_ptr<T> create_shared(Args&&... args) 
{ 
    class HelperClass : public T 
    { 
    public: 
     HelperClass (Args&& ... nargs) : T(std::forward<Args...>(nargs)...) {} 
     virtual ~HelperClass() = default; 
    }; 

    return std::make_shared<HelperClass>(std::forward<Args...>(args)...); 
} 
#else 
template<typename T, typename... Args> 
std::shared_ptr<T> create_shared(Args... args) 
{ 
    class HelperClass : public T 
    { 
    public: 
     HelperClass (Args ... nargs) : T(nargs...) {} 
     virtual ~HelperClass() = default; 
    }; 

    return std::make_shared<HelperClass>(args...); 
} 
#endif 

#endif 

staticinitclass.h

#ifndef STATICINITCLASS_H_ 
#define STATICINITCLASS_H_ 

class StaticInitClass 
{ 
public: 
#ifdef INITIALIZE_IN_HEADER 
    static const int default_i = 1; 
#else 
    static const int default_i; 
#endif 
    virtual ~StaticInitClass() = default; 
    StaticInitClass() = delete; 
protected: 
    StaticInitClass(int i); 
}; 

#endif 

staticinitclass.cpp:

#include "staticinitclass.h" 

#include <iostream> 

#ifndef INITIALIZE_IN_HEADER 
const int StaticInitClass::default_i = 2; 
#endif 

StaticInitClass::StaticInitClass(int i) 
{ 
    std::cout << "Created with " << i << std::endl; 
} 

main.cpp:

#include "staticinitclass.h" 
#include "createshared.h" 
#include <memory> 

int main(int argc, const char* argv[]) 
{ 
    auto shared = create_shared<StaticInitClass>(StaticInitClass::default_i); 
} 

Без флагов программа компилируется и работает нормально.

$ g++ -std=c++11 main.cpp staticinitclass.cpp 
$ ./a.out 
Created with 2 

Fine, потому что default_i является целочисленный тип, мы можем инициализировать его в заголовке. Давайте сделаем это

$ g++ -std=c++11 main.cpp staticinitclass.cpp -DINITIALIZE_IN_HEADER 
$ ./a.out 
Created with 1 

Хорошо, по-прежнему собирает и работает нормально. Теперь, давайте добавим наш & & и зЬй :: вперед

$ g++ -std=c++11 main.cpp staticinitclass.cpp -DINITIALIZE_IN_HEADER -DUSE_REFREF 
/tmp/cc3G4tjc.o: In function `main': 
main.cpp:(.text+0xaf): undefined reference to `StaticInitClass::default_i' 
collect2: error: ld returned 1 exit status 

Linker ошибку. Ну, давайте попробуем инициализацию нашего default_i члена в .cpp

$ g++ -std=c++11 main.cpp staticinitclass.cpp -DUSE_REFREF 
$ ./a.out 
Created with 2 

И снова работает. Использование clang дает тот же результат, который заставил бы меня поверить, что это не просто изолированная ошибка компилятора, а, возможно, что-то в самом языке, который предотвращает статическую инициализацию. Я просто не могу объяснить, почему добавление & & приведет к перерыву.

В настоящее время я использую г ++ 4.8.2 и лязг ++ 3.5 на Ubuntu 14.04

Любые идеи, что сломана здесь при использовании -DINITIALIZE_IN_HEADER и -DUSE_REFREF?

+0

Предоставление инициализатора для статического элемента данных не означает, что вы также предоставили определение. Когда вы объединяете две опции '-D', вы в конечном итоге делаете odr-использование этого статического члена и, следовательно, должны предоставить определение для него. – Praetorian

ответ

2

Следуя §9.4.2 [class.static.data]:

Если энергонезависимая Const статический член данных интегрального или перечисления типа, его заявление в определении класса можно указать логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением присваивания, является постоянным выражением (5.19). [...] Член все еще должен быть определен в области пространства имен, если в программе используется odr (3.2), а определение области пространства имен не должно содержать инициализатор.

Другими словами, предоставление константного статического члена данных значение непосредственно в заголовке не означает, что вам не нужно определять этот элемент данных. Вы должны иметь это в staticinitclass.CPP файл:

#ifndef INITIALIZE_IN_HEADER 
const int StaticInitClass::default_i = 2; 
#else 
const int StaticInitClass::default_i; // this is what you don't have 
#endif 

Связывание ссылки (для ссылки переадресации && в вашем случае, выведенной в качестве константной ссылке Lvalue) считается ODR использования этого элемента данных.

В случае, если вы не используете ссылку на пересылку, и вы принимаете аргумент по значению, то это не является нечетным использованием этого статического члена данных, поэтому ошибка компоновщика не возникает.

+0

Недостающая часть состоит в том, что просто использование значения не учитывается как использование odr. –

+0

@ T.C. добавлено это также –

+0

А! Я совершенно не знал о спецификации, используемой odr. Благодаря! – ilektron

Смежные вопросы