статичні конструктори в C ++? Мені потрібно ініціалізувати приватні статичні об’єкти


176

Я хочу мати клас із приватним статичним членом даних (вектор, який містить усі символи az). У Java або C # я просто можу зробити "статичний конструктор", який запуститься до того, як я зроблю будь-які екземпляри класу, і встановить статичні дані членів класу. Він запускається лише один раз (оскільки змінні читаються лише і їх потрібно встановити лише один раз), і оскільки це функція класу, він може отримати доступ до своїх приватних членів. Я можу додати код у конструктор, який перевіряє, чи не ініціалізується вектор, і ініціалізувати його, якщо його немає, але це вводить багато необхідних перевірок і не здається оптимальним рішенням проблеми.

Мені приходить думка, що оскільки змінні будуть читатися лише вони, вони можуть бути просто загальнодоступними статичними const, тому я можу їх встановити один раз поза класом, але ще раз, здається, це як некрасивий злом.

Чи можливо мати приватні статичні члени даних у класі, якщо я не хочу ініціалізувати їх у конструкторі екземплярів?



1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 Це питання зосереджено на запуску коду для ініціалізації приватних статичних об'єктів , не встановлення постійних значень приватних статичних примітивних типів. Рішення різні.
Гордон Густафсон

ах, я думаю, ти маєш рацію, відступаючи.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Відповіді:


180

Щоб отримати еквівалент статичного конструктора, вам потрібно написати окремий звичайний клас для зберігання статичних даних, а потім зробити статичний екземпляр цього звичайного класу.

class StaticStuff
{
     std::vector<char> letters_;

public:
     StaticStuff()
     {
         for (char c = 'a'; c <= 'z'; c++)
             letters_.push_back(c);
     }

     // provide some way to get at letters_
};

class Elsewhere
{
    static StaticStuff staticStuff; // constructor runs once, single instance

};

12
Дякую! хоча це дуже прикро робити це все. Одна з багатьох "помилок" C # і java, з яких навчились.
Гордон Густафсон

109
Так. Я завжди зазначаю людям, що якби C ++ не робив усіх цих "помилок", то інші мови повинні були б їх робити. C ++, що охоплює стільки ґрунту, навіть помиляючись, був чудовим для мов, які слідували за ним.
кварк

11
Всього один невеликий нюанс, оскільки конструктори вступають у гру, ніхто не гарантує, коли конструктор для статичного об'єкта виконується. Відомий набагато безпечніший підхід - клас Elsewhere {StaticStuff & get_staticStuff () {static StaticStuff staticStuff; // конструктор запускається один раз, коли комусь спочатку потрібно повернути staticStuff; }}; Цікаво, чи статичні конструктори в C # і Java можуть надавати ту саму гарантію, що і код вище ...
Олег Жилін

13
@Oleg: Так. Стандартні гарантії, що конструктори для всіх не локальних змінних виконуються до введення основних. Він також гарантує, що в складі підрозділу компіляції чітко визначений порядок побудови та такий самий порядок, що і декларація в складі одиниці складання. На жаль, вони не визначають порядок у кількох одиницях компіляції.
Мартін Йорк

13
Це насправді випадок, коли friendмає багато сенсу, щоб клас Elsewhereміг легко отримати доступ StaticStuffдо внутрішніх даних (не можу додати інкапсуляцію будь-яким небезпечним способом).
Конрад Рудольф

81

Ну ви можете мати

class MyClass
{
    public:
        static vector<char> a;

        static class _init
        {
          public:
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;
};

Не забувайте (у .cpp) це:

vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;

Програма як і раніше посилатиметься без другого рядка, але ініціалізатор не буде виконуватися.


+1 (не випробував) Але: Коли викликається ctor _init._init ()? До або після ctor MyClass, коли у мене є статичний об’єкт MyClass? Я думаю, ви не можете сказати ...
ур.

2
привіт, де я можу дізнатися більше про цю магію "ініціалізатора"?
Карел Білек

Чи не повинно бути MyClass::a.push_back(i)замість цього a.push_back(i)?
Ніл Басу

4
@ur .: _initializerє суб'єктом MyClass. Суб’єкти ініціалізуються в такому порядку: віртуальний суб’єкт базового класу, в першу глибину, зліва направо (але лише ініціалізуючи кожен окремий субект один раз); потім прості суб'єкти базового класу, в порядку першого поглиблення, зліва направо; потім член суб'єктів у порядку декларації. Тому безпечно використовувати стратегію EFraim, за умови, що код _initialiserстосується лише членів, заявлених перед ним.
j_random_hacker

2
Ця відповідь краща за прийняту, оскільки автор згадав про незамінну ініціалізацію у другому ролику коду.
Джефф Т.

33

C ++ 11 розчин

Оскільки C ++ 11, ви можете просто використовувати лямбда-вирази для ініціалізації членів статичного класу. Це навіть працює, якщо вам потрібно накласти порядок побудови між різними статичними елементами або якщо у вас є статичні елементи, які є const.

Файл заголовка:

class MyClass {
    static const vector<char> letters;
    static const size_t letterCount;
};

Вихідний файл:

// Initialize MyClass::letters by using a lambda expression.
const vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

// The initialization order of static members is defined by the order of
// definition within the source file, so we can access MyClass::letters here.
const size_t MyClass::letterCount = letters.size();

цікаве рішення. у цьому випадку, якщо я кидаю виняток, хто може його зловити?
rafi wiener

5
Статичний код ініціалізації програми ніколи не повинен викидати жодних винятків, інакше програма вийде з ладу. Ви повинні загорнути логіку ініціалізатора в try catchблок, якщо можуть бути викинуті винятки.
emkey08

19

У файлі .h:

class MyClass {
private:
    static int myValue;
};

У файлі .cpp:

#include "myclass.h"

int MyClass::myValue = 0;

5
Це чудово працює для окремих статичних членів (незалежно від типу). Недолік в порівнянні зі статичними конструкторами полягає в тому, що ви не можете нав'язувати порядок між різними статичними елементами. Якщо вам потрібно це зробити, дивіться відповідь Earwicker.
кварк

Я роблю саме це, але він все ще не складається. І в ньому сказано, що це проблема (у конструкторі, а не в заголовку)
Flotolk

14

Ось ще один підхід, схожий на Деніела Ервікера, який також використовує пропозицію Конрада Рудольфа про друзі. Тут ми використовуємо внутрішній приватний клас утиліти друга для ініціалізації статичних членів вашого основного класу. Наприклад:

Файл заголовка:

class ToBeInitialized
{
    // Inner friend utility class to initialize whatever you need

    class Initializer
    {
    public:
        Initializer();
    };

    friend class Initializer;

    // Static member variables of ToBeInitialized class

    static const int numberOfFloats;
    static float *theFloats;

    // Static instance of Initializer
    //   When this is created, its constructor initializes
    //   the ToBeInitialized class' static variables

    static Initializer initializer;
};

Файл реалізації:

// Normal static scalar initializer
const int ToBeInitialized::numberOfFloats = 17;

// Constructor of Initializer class.
//    Here is where you can initialize any static members
//    of the enclosing ToBeInitialized class since this inner
//    class is a friend of it.

ToBeInitialized::Initializer::Initializer()
{
    ToBeInitialized::theFloats =
        (float *)malloc(ToBeInitialized::numberOfFloats * sizeof(float));

    for (int i = 0; i < ToBeInitialized::numberOfFloats; ++i)
        ToBeInitialized::theFloats[i] = calculateSomeFancyValue(i);
}

Цей підхід має перевагу в тому, щоб повністю приховати клас Initializer від зовнішнього світу, зберігаючи все, що міститься в класі, для ініціалізації.


+1 Наведемо приклад, який зберігає реалізацію у власному файлі.
Ендрю Ларссон

1
Крім того, ви повинні переконатися, що ToBeInitialized::Initializer::Initializer()викликається, тому вам потрібно додати ToBeInitialized::Initializer ToBeInitialized::initializer;до файлу реалізації. Я взяв деякі речі з вашої ідеї та з ідеї EFraim, і вона працює точно так, як мені потрібно, і виглядає чистою. Спасибі, чоловіче.
Ендрю Ларссон

11

Test::StaticTest() викликається рівно один раз під час глобальної статичної ініціалізації.

Користувачеві потрібно лише додати один рядок до функції, яка повинна стати їх статичним конструктором.

static_constructor<&Test::StaticTest>::c;примушує ініціалізацію cпід час глобальної статичної ініціалізації.

template<void(*ctor)()>
struct static_constructor
{
    struct constructor { constructor() { ctor(); } };
    static constructor c;
};

template<void(*ctor)()>
typename static_constructor<ctor>::constructor static_constructor<ctor>::c;

/////////////////////////////

struct Test
{
    static int number;

    static void StaticTest()
    {
        static_constructor<&Test::StaticTest>::c;

        number = 123;
        cout << "static ctor" << endl;
    }
};

int Test::number;

int main(int argc, char *argv[])
{
    cout << Test::number << endl;
    return 0;
}

Це фантастичне рішення. Мені дуже подобається відповідь Дугласа Манделя , але це ще більш стисло.
FlintZA

Це справді дивовижно!
nh_

9

init()Функція не потрібна , std::vectorїї можна створити з діапазону:

// h file:
class MyClass {
    static std::vector<char> alphabet;
// ...
};

// cpp file:
#include <boost/range.hpp>
static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
std::vector<char> MyClass::alphabet( boost::begin( ::alphabet ), boost::end( ::alphabet ) );

Однак зауважте, що статистика типу класу створює проблеми в бібліотеках, тому їх слід уникати там.

C ++ 11 Оновлення

Що стосується C ++ 11, ви можете зробити це замість цього:

// cpp file:
std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' };

Це семантично еквівалентне рішенню C ++ 98 у вихідній відповіді, але ви не можете використовувати літеральний рядок з правого боку, тому він не є абсолютно кращим. Однак, якщо у Вас є вектор будь-якого іншого типу , ніж char, wchar_t, char16_tабо char32_t(масиви з яких може бути записані в вигляді строкових літералів), версія C ++ 11 буде строго видалити шаблонний код без введення іншого синтаксису, по порівнянні з C ++ 98 версія.


Мені це подобається. Хоча якби ми могли це зробити в один рядок без невдалого алфавіту.
Мартін Йорк

Чи не має значення проблем із бібліотеками, чи статичний клас приватний чи загальнодоступний? Крім того, чи має значення, чи бібліотека статична (.a) чи динамічна (.so)?
Захарій Краус

@ZacharyKraus: що таке громадський / приватний клас ? І ні, хоча проблеми різні, але перекриваються, не має значення, чи бібліотека пов'язана статично чи динамічно.
Marc Mutz - mmutz

@ MarcMutz-mmutz Вибачте за використання публічного / приватного класу, що не відповідає правильній термінології C ++. Те, про що я мав на увазі, - це рішення EFraim вище. Однак у моїй версії я зробив приватний член класу приватним. Я намагався зрозуміти, чи має значення статичний член класу як публічного чи приватного значення для розвитку бібліотеки та зручності використання. Мій кишечник говорить мені, що це не повинно впливати на бібліотеку, оскільки користувачі ніколи не матимуть доступу ні до статичного класу, ні до об'єкта, що його будує, але я хотів би отримати певну розумність гуру з цієї теми.
Захарій Краус

@ZacharyKraus: Основна проблема зі статикою, яка потребує динамічної ініціалізації ([basic.start.init] / 2), полягає в тому, що вони виконують код. У бібліотеках може статися, що код бібліотеки вже вивантажений під час запуску деструкторів. Якщо ви хочете почути більше, я пропоную написати про це питання.
Марк Муц - mmutz

6

Поняття статичних конструкторів було введено на Java після того, як вони дізналися про проблеми C ++. Тож у нас немає прямого еквівалента.

Найкращим рішенням є використання типів POD, які можна ініціалізувати явно.
Або зробіть статичним членам певний тип, який має власний конструктор, який буде ініціалізувати його правильно.

//header

class A
{
    // Make sure this is private so that nobody can missues the fact that
    // you are overriding std::vector. Just doing it here as a quicky example
    // don't take it as a recomendation for deriving from vector.
    class MyInitedVar: public std::vector<char>
    {
        public:
        MyInitedVar()
        {
           // Pre-Initialize the vector.
           for(char c = 'a';c <= 'z';++c)
           {
               push_back(c);
           }
        }
    };
    static int          count;
    static MyInitedVar  var1;

};


//source
int            A::count = 0;
A::MyInitedVar A::var1;

4

При спробі складання та використання класу Elsewhere(з відповіді Earwicker ) я отримую:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A)

Здається, неможливо ініціалізувати статичні атрибути не цілих типів, не ставлячи якийсь код поза визначенням класу (CPP).

Щоб зробити цю компіляцію, ви можете використовувати " статичний метод із статичною локальною змінною всередині ". Щось на зразок цього:

class Elsewhere
{
public:
    static StaticStuff& GetStaticStuff()
    {
        static StaticStuff staticStuff; // constructor runs once, single instance
        return staticStuff;
    }
};

І ви також можете передавати аргументи конструктору або ініціалізувати його з певними значеннями, він дуже гнучкий, потужний і простий у реалізації ... єдине, у вас є статичний метод, що містить статичну змінну, а не статичний атрибут ... синтаксис трохи змінюється, але все ж корисний. Сподіваюся, це комусь корисно,

Уго Гонсалес Кастро.


Хоча будьте обережні, якщо використовуєте нитки. Я вважаю, що в GCC конструкція статичних місцевих жителів захищена від одночасного виконання, але у Visual C ++ це не так.
Даніель Ервікер

1
Від C ++ 11 року, і в POSIX, він повинен бути потокобезпечна.
Marc Mutz - mmutz

Мені дуже сподобалися два інші рішення вище ( це та це ), але ваше - єдине, що забезпечує ініціалізацію статики у тому порядку, який їм потрібен у бібліотеках. Я просто маю приватний статичний метод екземпляра, як ваш вище, і охоплюю доступ до інших значень у публічних статичних аксесуарах, які використовують цей метод екземпляра замість прямих посилань. Дякую.
FlintZA

Дивовижно! Це завершує його.
Гейб Халсмер

4

Я думаю, простим рішенням цього буде:

    //X.h
    #pragma once
    class X
    {
    public:
            X(void);
            ~X(void);
    private:
            static bool IsInit;
            static bool Init();
    };

    //X.cpp
    #include "X.h"
    #include <iostream>

    X::X(void)
    {
    }


    X::~X(void)
    {
    }

    bool X::IsInit(Init());
    bool X::Init()
    {
            std::cout<< "ddddd";
            return true;
    }

    // main.cpp
    #include "X.h"
    int main ()
    {
            return 0;
    }

Так я і роблю.
Етереалоне

1

Щойно вирішився той самий трюк. Мені довелося вказати визначення одиночного статичного члена для Singleton. Але зробити складніше - я вирішив, що я не хочу називати ctor RandClass (), якщо я не буду його використовувати ... саме тому я не хотів ініціалізувати синглтон в усьому світі у своєму коді. Також я додав простий інтерфейс у своєму випадку.

Ось підсумковий код:

Я спростив код і використовую функцію rand () та її єдиний насіннєвий srand ()

interface IRandClass
{
 public:
    virtual int GetRandom() = 0;
};

class RandClassSingleton
{
private:
  class RandClass : public IRandClass
  {
    public:
      RandClass()
      {
        srand(GetTickCount());
      };

     virtual int GetRandom(){return rand();};
  };

  RandClassSingleton(){};
  RandClassSingleton(const RandClassSingleton&);

  // static RandClass m_Instance;

  // If you declare m_Instance here you need to place
  // definition for this static object somewhere in your cpp code as
  // RandClassSingleton::RandClass RandClassSingleton::m_Instance;

  public:

  static RandClass& GetInstance()
  {
      // Much better to instantiate m_Instance here (inside of static function).
      // Instantiated only if this function is called.

      static RandClass m_Instance;
      return m_Instance;
  };
};

main()
{
    // Late binding. Calling RandClass ctor only now
    IRandClass *p = &RandClassSingleton::GetInstance();
    int randValue = p->GetRandom();
}
abc()
{
    IRandClass *same_p = &RandClassSingleton::GetInstance();
}

1

Ось мій варіант рішення EFraim; відмінність полягає в тому, що завдяки неявній екземплярі шаблону статичний конструктор викликається лише в тому випадку, якщо створені екземпляри класу, і що жодне визначення у .cppфайлі не потрібне (завдяки магії інстанціювання шаблону).

У .hфайлі у вас є:

template <typename Aux> class _MyClass
{
    public:
        static vector<char> a;
        _MyClass() {
            (void) _initializer; //Reference the static member to ensure that it is instantiated and its initializer is called.
        }
    private:
        static struct _init
        {
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;

};
typedef _MyClass<void> MyClass;

template <typename Aux> vector<char> _MyClass<Aux>::a;
template <typename Aux> typename _MyClass<Aux>::_init _MyClass<Aux>::_initializer;

У .cppфайлі ви можете мати:

void foobar() {
    MyClass foo; // [1]

    for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) {
        cout << *it;
    }
    cout << endl;
}

Зверніть увагу, що MyClass::aініціалізується лише в тому випадку, якщо рядок [1] ​​є, тому що він викликає (і вимагає інстанціювання) конструктора, який вимагає інстанції _initializer.


1

Ось ще один метод, коли вектор приватний до файлу, який містить реалізацію, використовуючи анонімний простір імен. Це корисно для таких речей, як таблиці пошуку, які є приватними для реалізації:

#include <iostream>
#include <vector>
using namespace std;

namespace {
  vector<int> vec;

  struct I { I() {
    vec.push_back(1);
    vec.push_back(3);
    vec.push_back(5);
  }} i;
}

int main() {

  vector<int>::const_iterator end = vec.end();
  for (vector<int>::const_iterator i = vec.begin();
       i != end; ++i) {
    cout << *i << endl;
  }

  return 0;
}

Хоча, можливо, ви хочете назвати Iі iщось трохи більш незрозуміле, щоб ви випадково не використовували їх десь нижче у файлі.
Джим Хунзікер

1
Якщо чесно, важко зрозуміти, чому хтось хотів би використовувати приватні статичні члени, а не анонімні простори імен у файлах реалізації.
Джим Ханзікер

1

Це, звичайно, не повинно бути таким складним, як прийнята в даний час відповідь (Деніел Ервікер). Клас зайвий. У цьому випадку немає потреби в мовній війні.

.hpp-файл:

vector<char> const & letters();

.cpp файл:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}


0

Ви визначаєте статичні змінні члена аналогічно тому, як визначаєте методи членів.

foo.h

class Foo
{
public:
    void bar();
private:
    static int count;
};

foo.cpp

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;

2
Питання CrazyJugglerDrummer стосувалося не статичного простого старого типу даних :)
jww

0

Щоб ініціалізувати статичну змінну, ви просто зробите це всередині вихідного файлу. Наприклад:

//Foo.h
class Foo
{
 private:
  static int hello;
};


//Foo.cpp
int Foo::hello = 1;

Питання CrazyJugglerDrummer стосувалося не статичного простого старого типу даних :)
jww

0

Як щодо створення шаблону для імітації поведінки C #.

template<class T> class StaticConstructor
{
    bool m_StaticsInitialised = false;

public:
    typedef void (*StaticCallback)(void);

    StaticConstructor(StaticCallback callback)
    {
        if (m_StaticsInitialised)
            return;

        callback();

        m_StaticsInitialised = true;
    }
}

template<class T> bool StaticConstructor<T>::m_StaticsInitialised;

class Test : public StaticConstructor<Test>
{
    static std::vector<char> letters_;

    static void _Test()
    {
        for (char c = 'a'; c <= 'z'; c++)
            letters_.push_back(c);
    }

public:
    Test() : StaticConstructor<Test>(&_Test)
    {
        // non static stuff
    };
};

0

Для простих випадків, таких як статична змінна, обернута всередині функції статичного члена, майже так само добре. Це просто і зазвичай оптимізується компіляторами. Це не вирішує проблему порядку ініціалізації для складних об'єктів.

#include <iostream>

class MyClass 
{

    static const char * const letters(void){
        static const char * const var = "abcdefghijklmnopqrstuvwxyz";
        return var;
    }

    public:
        void show(){
            std::cout << letters() << "\n";
        }
};


int main(){
    MyClass c;
    c.show();
}

0

Це рішення?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};

0

Статичний конструктор може бути емульований, використовуючи клас друзів або вкладений клас, як показано нижче.

class ClassStatic{
private:
    static char *str;
public:
    char* get_str() { return str; }
    void set_str(char *s) { str = s; }
    // A nested class, which used as static constructor
    static class ClassInit{
    public:
        ClassInit(int size){ 
            // Static constructor definition
            str = new char[size];
            str = "How are you?";
        }
    } initializer;
};

// Static variable creation
char* ClassStatic::str; 
// Static constructor call
ClassStatic::ClassInit ClassStatic::initializer(20);

int main() {
    ClassStatic a;
    ClassStatic b;
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    a.set_str("I am fine");
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    std::cin.ignore();
}

Вихід:

String in a: How are you?
String in b: How are you?
String in a: I am fine
String in b: I am fine

Чому ви newвикористовуєте масив char лише для того, щоб негайно просочити вказівник і перезаписати його
Ерік

0

Нічого собі, я не можу повірити, що ніхто не згадав найбільш очевидну відповідь, і той, який найбільш близько імітує поведінку статичного конструктора C #, тобто його не викликають, поки не буде створений перший об'єкт цього типу.

std::call_once()доступний на C ++ 11; якщо ви не можете використовувати це, це можна зробити за допомогою статичної булевої змінної класу та атомної операції порівняння та обміну. У своєму конструкторі подивіться, чи можна атомним чином змінити прапор класу статичного з falseна true, і якщо так, ви можете запустити код статичної побудови.

Для отримання додаткового кредиту зробіть його тристороннім прапором замість булевого, тобто не бігайте, бігаючи та не виконуючи біг. Тоді всі інші екземпляри цього класу можуть закручуватися до тих пір, поки екземпляр, на якому працює static-конструктор, не завершиться (тобто видає огорожу пам'яті, а потім встановить стан на "виконане виконання"). Ваш спін-замок повинен виконати інструкцію "паузи" процесора, щоразу подвоювати очікування до досягнення порогу тощо. - досить стандартна техніка блокування віджиму.

За відсутності C ++ 11, це має розпочати роботу.

Ось декілька псевдокодів, щоб навести вас. Поставте це у своєму класі визначення:

enum EStaticConstructor { kNotRun, kRunning, kDone };
static volatile EStaticConstructor sm_eClass = kNotRun;

І це у вашому конструкторі:

while (sm_eClass == kNotRun)
{
    if (atomic_compare_exchange_weak(&sm_eClass, kNotRun, kRunning))
    {
        /* Perform static initialization here. */

        atomic_thread_fence(memory_order_release);
        sm_eClass = kDone;
    }
}
while (sm_eClass != kDone)
    atomic_pause();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.