с++, инициализация статических членов класса в .h файле

Phoenix

Приводит к multiple definition of `NS::Timer::start_timer'
Как поступать с функциями понятно, сделать их inline, тогда они с друг другом не конфликтуют.
По аналогии, можно класс и инициализацию включить в namespaece { ... }, но тогда в каждом .cpp будет юзаться свой экземпляр класса, а значит статик переменная будет не общая.
Есть ещё выход, чтобы не создавать static.cpp с одной строчкой?
  $ vim one.cpp 
$ g++ -c one.cpp
$ g++ -c twomain.cpp
$ g++ one.o twomain.o -o prog
twomain.o:(.bss+0x0): multiple definition of `NS::Timer::start_timer'
one.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status



$ cat one.cpp
#include "static.h"


void one_func
{
NS::Timer::get_time(1000);
}



$ cat twomain.cpp
#include "static.h"
void one_func;


int main
{
NS::Timer::get_time(100000);
one_func;
}
$7:18 garry ...testapp/tempclass(0/1)$ cat static.h
namespace NS
{

//namespace{
class Timer
{
public:
static long long int start_timer;
static long long int get_time(long long int now)
{
if(start_timer == 0)
{
start_timer = now;
return 0;
}
return now - start_timer;
}
};
long long int Timer::start_timer = 0;
//}
void inline super_func
{

}

}

evgen5555

#ifndef _STATIC_H_
#define _STATIC_H_
...
#endif

Phoenix

на линковке ломается же. Не поможет.

yolki

не совсем понятно, что нужно.
Нужен класс со статическим полем (= поле, разделяемое между экземплярами)?
тогда его не надо занулять - оно автоматически будет нулём инициализировано.
то есть вот эта строка - лишняя:

long long int Timer::start_timer = 0;

yolki

Вот так надо:

quad ~/forumlocal/cpptest $ cat main.cpp
#include <iostream>
#include "static.h"

void foo;
void bar;

using namespace std;

int main
{
cout << NS::Timer::start_timer << endl;
foo;
bar;
return 0;
}
quad ~/forumlocal/cpptest $ cat user1.cpp
#include <iostream>
#include "static.h"

using namespace std;
void foo
{
cout << NS::Timer::get_time(200) << endl;
}
quad ~/forumlocal/cpptest $ cat user2.cpp
#include <iostream>
#include "static.h"

using namespace std;
void bar
{
cout << NS::Timer::get_time(300) << endl;
}
quad ~/forumlocal/cpptest $ cat static.h
namespace NS {
class Timer
{
public:
static long long start_timer;

static long long get_time(long long now)
{
if(start_timer==0)
{ start_timer=now; return 0; }
return now-start_timer;
}
};
};
quad ~/forumlocal/cpptest $ cat static.cpp

#include "static.h"


long long NS::Timer::start_timer =0;

quad ~/forumlocal/cpptest $ g++ main.o user1.o user2.o static.o -o main

Phoenix

Есть ещё выход, чтобы не создавать static.cpp с одной строчкой?

:)
вообще, да, наверно проще выкинут эту строку, раз всё равно нулём инициализируется

yolki

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

Phoenix

там же всё статик. Экземпляр же пустой будет

Serab

#ifndef _STATIC_H_
#define _STATIC_H_
...
#endif
то есть вот эта строка - лишняя:
всё равно придётся создать экземпляр класса.
но проще выкинут эту строку, раз всё равно нулём инициализируется

tamusyav

Нужен класс со статическим полем (= поле, разделяемое между экземплярами)?
тогда его не надо занулять - оно автоматически будет нулём инициализировано.
то есть вот эта строка - лишняя:
long long int Timer::start_timer = 0;
Не совсем так. Даже если твой компилятор позволяет опустить определение статического поля, инициализируемого по умолчанию, на других компиляторах это может не работать (только что проверил MSVS2012 и gcc 4.4.6, - определение необходимо).

There shall be exactly one definition of a static data member that is odr-used (3.2) in a program; no diagnostic is required.
Даже если компилятор поддерживает отсутствие определения в такой ситуации, пользоваться этим я бы не стал, потому что тогда из любого cpp-файла можно изменить поведение класса, добавив это определение с любым инициализатором. Если же определение есть, то добавление второго приведет к конфликту.
По новому стандарту можно также указать инициализатор в объявлении (в теле класса) при условии, что инициализатор - константное выражение. Это, однако, не отменяет требование наличия определения (соответственно, уже без инициализатора). Но такой синтаксис пока поддерживается, если не ошибаюсь, только g++ и clang.
если строчку выкинешь, тебе всё равно придётся создать экземпляр класса.
Статические поля создаются независимо от того, создаются ли экземпляры класса. Время жизни у них такое же, как у глобальных переменных.

Static data members are initialized and destroyed exactly like non-local variables

Phoenix

в общем всё понятно. Спасибо.
Аналог inline это анонимный ns, но тогда будем иметь несколько экземпляров этой глобальной переменной (в каждом .o файле)
Я надеялся, что есть какой-то такой механизм:
в каждом .o файле появляется по своему экземпляру переменной, но во время линковки выбирается какой-то один. Но это скорее даже нужна поддержка не столько со стороны языка, сколько линковщика и всех архитектуры этой линковки

rosali

> Есть ещё выход, чтобы не создавать static.cpp с одной строчкой?
Есть! Но чё-то я не уверен, что он тебе понравится =)
 
[xoft c++]$ cat static.h
#pragma once

template<class T>
struct _C {
static int x;
};

template<class T>
int _C<T>::x = 100;

typedef _C<void> C;

[xoft c++]$ cat static1.cpp
#include "static.h"

int f {
return C::x;
}

[xoft c++]$ cat static2.cpp
#include "static.h"
#include <iostream>

using namespace std;

int f;

int g {
return C::x + 1;
}

int main {
cout << f + g << endl;
}

[xoft c++]$ g++ -c static1.cpp

[xoft c++]$ g++ -c static2.cpp

[xoft c++]$ g++ -o static static1.o static2.o

[xoft c++]$ ./static
201

Phoenix

ну вот! То, что нужно.
А как так получается? Где хранится _C<void>::x ?
немного изменил пример, чтобы было видно, что переменная общая:
 
$ cat static.h
#pragma once

template<class T>
struct _C {
static int x;
};

template<class T>
int _C<T>::x = 100;

typedef _C<void> C;

$ cat static1.cpp
#include "static.h"

int f {
return ++C::x;
}

$ cat static2.cpp
#include "static.h"
#include <iostream>

using namespace std;

int f;

int g {
return ++C::x;
}

int main {
cout << f + g << endl;
}
$ g++ -c static1.cpp
$ g++ -c static2.cpp
$ g++ -o static static1.o static2.o
$ ./static
203


tamusyav

Ну, если уж на то пошло, то инициализировать лучше так:
template<>
int C::x = 100;

Иначе можно создать еще один файл, который будет инициализировать эту переменную по-своему с помощью аналогичной строчки (причем в зависимости от компилятора и прочих факторов будет использоваться либо это определение, либо определение из static.h).
Но, имхо, все равно грязновато выглядит. Всего лишь для того, чтобы не писать cpp-файл из двух строк, пишется десяток в хедере... Понятней код от этого точно не станет.
Кстати, судя по начальному посту, реализуется счет времени. <chrono> или Boost.Chrono использовать нельзя?

apl13

#pragma once
Закопайте стюардессу!

apl13

Всего лишь для того, чтобы не писать cpp-файл из двух строк, пишется десяток в хедере...
#define HEAVENLY_STATIC(C, int, x)      \
template<class T> struct _C { \
static int x; \
}; \
\
template<class T> int _C<T>::x = 100; \
\
typedef _C<void> C

HEAVENLY_STATIC(C, int, x);

:D

erotic

Сделай вместо класса Timer со статическим членом в анонимном неймспейсе класс Timer в нормальном неймспейсе и функцией вместо члена:

class Timer
{
public:
static long long int& start_timer
{
static long long int v = 0;
return v;
}
static long long int get_time(long long int now)
{
if(start_timer == 0)
{
start_timer = now;
return 0;
}
return now - start_timer;
}
};

Функция будет inline, а компилятор гарантирует, что во всех единицах трансляции у нее будет одна и та же статическая переменная. Собсно, это тот же синглтон, только частный случай в твоем классе.
Оставить комментарий
Имя или ник:
Комментарий: