Вопрос по static, private, initializer, c++, static-constructor – статические конструкторы в C ++? Мне нужно инициализировать частные статические объекты

162

Я хочу иметь класс с закрытым статическим членом данных (вектор, который содержит все символы a-z). В Java или C # я могу просто создать «статический конструктор». он запустится до того, как я создам экземпляры класса, и настроит статические члены-данные класса. Он запускается только один раз (поскольку переменные доступны только для чтения и должны быть установлены только один раз), и поскольку он является функцией класса, он может обращаться к своим закрытым членам. Я мог бы добавить код в конструктор, который проверяет, инициализирован ли вектор, и инициализировать его, если это не так, но он вводит много необходимых проверок и не кажется оптимальным решением проблемы.

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

Is it possible to have private static data members in a class if I don't want to initialize them in the instance constructor?

Ах, я думаю, что вы правы, отказываясь. Ciro Santilli 新疆改造中心 六四事件 法轮功
@ CiroSantilli & # x65B0; & # x7586; & # x6539; & # x9020; & # x4E2D; & # x5FC3; & # x516D; & # x56DB; & # x4E8B; & # x4EF6; & # x6CD5; & # x8F6E; & # x529F; Этот вопрос сфокусирован на запуске кода для инициализации приватной статики.objects, не устанавливая постоянные значения частных статических примитивных типов. Решения разные. Gordon Gustafson

Ваш Ответ

21   ответ
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 что-то более неясное, поэтому вы не можете случайно использовать их где-то ниже в файле.
Честно говоря, трудно понять, почему кто-то хотел бы использовать частные статические члены, а не анонимные пространства имен в файлах реализации.
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

Это решение?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};
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;
    }
Я тоже так делаю.
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();
}
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.

0

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

std::call_once() доступно в C ++ 11; если вы не можете использовать это, это можно сделать с помощью статической логической переменной класса и атомарной операции сравнения и обмена. В вашем конструкторе посмотрите, можете ли вы атомарно изменить флаг class-static сfalse вtrueи, если это так, вы можете запустить код статической конструкции.

Для дополнительного кредита сделайте его трехсторонним флагом вместо логического значения, то есть не запускайте, не выполняйте, а выполняйте. Затем все другие экземпляры этого класса могут вращаться до тех пор, пока экземпляр, на котором запущен статический конструктор, не завершит работу (то есть выдаст забор памяти, а затем установит состояние «завершено выполнение»). Ваша спин-блокировка должна запускать процессорную паузу "пауза" инструкции, удваивайте время ожидания каждый раз до порога и т. д. & # x2014; довольно стандартная техника блокировки вращения.

В отсутствие 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();
74

Ну, вы можете иметь

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;

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

Не должно ли это бытьMyClass::a.push_back(i) вместоa.push_back(i) ?
+1 (не пробовал) Но: когда вызывается ctor _init._init ()? До или после ctor MyClass, когда у меня есть статический объект MyClass? Я думаю, вы не можете сказать ...
Очень хорошее решение с низким уровнем шума! +1.
привет, где я могу найти больше об этом & quot; инициализаторе & quot; магия?
@ur .:_initializer это подобъектMyClass, Субобъекты инициализируются в следующем порядке: виртуальные субобъекты базового класса, в порядке глубины, слева направо (но инициализируют каждый отдельный подобъект только один раз); затем подобъекты простого базового класса, в порядке глубины, слева направо; затем член подобъектов в порядке объявления. Таким образом, безопасно использовать стратегию EFraim, при условии, что этот код в_initialiser относится только к членам, заявленным до него.
19

В файле .h:

class MyClass {
private:
    static int myValue;
};

В файле .cpp:

#include "myclass.h"

int MyClass::myValue = 0;
Я делаю именно это, но он по-прежнему не компилируется. И это говорит, что это проблемная область (в конструкторе, а не в заголовке)
Это прекрасно работает для отдельных статических членов (независимо от типа). Недостаток по сравнению со статическими конструкторами состоит в том, что вы не можете навязатьorder между различными статическими членами. Если вам нужно это сделать, см. Ответ Earwicker.
15

C++11 solution

Начиная с C ++ 11, вы можете просто использоватьлямбда-выражения инициализировать статические члены класса. Вам больше не нужно использовать обходные пути.

Header file:

class MyClass {
    static vector<char> letters;
};

Source file:

vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++) letters.push_back(c);
    return letters;
}();

Some notes:

  • You can initialize static members which depend on other static members. You just have to initialize them in the correct order.
  • The initialization order of multiple static members is well defined by the order of definition within the source file.
  • You can simply access all other private static members within the lambda expression.
интересное решение. в этом случае, если я выброшу исключение, кто может поймать его?
Статический код инициализации программы долженnever выкиньте любые исключения, иначе программа вылетит. Вы должны обернуть логику инициализатора вtry catch блокировать, если исключения могут быть выброшены.
169

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

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

};
Это на самом деле тот случай, когдаfriend имеет большой смысл, так что классElsewhere может легко получить доступStaticStuffВнутренние компоненты (я могу добавить, не нарушая инкапсуляцию).
Спасибо! хотя это очень раздражает, когда приходится все это делать. Одна из многих «ошибок» C # и Java извлекли уроки из. Gordon Gustafson
Да. Я всегда обращаю внимание людей на то, что если бы C ++ не сделал все эти «ошибки», тогда другие языки должны были бы сделать их. C ++, покрывающий так много основ, даже совершающий ошибки, отлично подходит для языков, которые следуют за ним.
@ Олег: Да, они делают. Стандарт гарантирует, что конструкторы для всех нелокальных переменных выполняются перед вводом main. Он также гарантирует, что в модуле компиляции порядок построения четко определен и такой же, как и в объявлении в модуле компиляции. К сожалению, они не определяют порядок для нескольких модулей компиляции.
Только один маленький нюанс, поскольку конструкторы вступают в игру, никто не гарантирует, когда конструктор для статического объекта выполняется. Хорошо известный более безопасный подход - это класс Elsewhere {StaticStuff & amp; get_staticStuff () {static StaticStuff staticStuff; // конструктор запускается один раз, когда кому-то это нужно сначала return staticStuff; }}; Интересно, могут ли статические конструкторы в C # и Java обеспечить ту же гарантию, что и код выше ...
0

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

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


//Foo.cpp
int Foo::hello = 1;
CrazyJugglerDrummer вопрос былnot о статически простой старый тип данных :)
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 За пример, который хранит реализацию в своем собственном файле.
Кроме того, вы должны убедиться, чтоToBeInitialized::Initializer::Initializer() вызывается, поэтому вам нужно добавитьToBeInitialized::Initializer ToBeInitialized::initializer; в файл реализации. Я взял некоторые вещи из вашей идеи и идеи EFraim, и она работает именно так, как мне нужно, и выглядит чистой. Спасибо чувак.
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 Update

Начиная с 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: Основная проблема со статикой, которая требует динамической инициализации ([basic.start.init] / 2), заключается в том, что они запускают код. В библиотеках может быть так, что код библиотеки уже выгружен при запуске деструкторов. Если вы хотите услышать больше, я предлагаю опубликовать вопрос об этом.
@ZacharyKraus: что является публичным / частнымclass? И нет, хотя проблемы разные, но перекрываются, не имеет значения, статически или динамически связана библиотека.
@ MarcMutz-mmutz Извините за использование публичного / частного класса, который не является правильной терминологией C ++. То, что я имел в виду, является решением EFraim выше. В моей версии я сделал статический член класса закрытым. Я пытался понять, влияет ли наличие статического члена класса как открытого или закрытого в разработке и удобстве использования библиотеки. Моя интуиция говорит мне, что это не должно влиять на библиотеку, потому что пользователи никогда не будут иметь доступа ни к статическому члену класса, ни к объекту, который его строит, но я хотел бы получить мудрость гуру в этой теме.
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;
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;
}
Это фантастическое решение. мне действительно нравитсяDouglas Mandel's answer также, но это еще более кратко.
Это действительно потрясающе!
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;
CrazyJugglerDrummer вопрос былnot о статически простой старый тип данных :)
4

При попытке скомпилировать иuse учебный классElsewhere (отОтвет Earwicker'а) Я получил:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" ([email protected]@@[email protected]@A)

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

Для компиляции вы можете использовать & quot;a static method with a static local variable inside& Quot; вместо. Что-то вроде этого:

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

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

Уго Гонц - Лез Кастро.

Мне очень понравилось два других решения выше (this а такжеthis), но только ваша обеспечивает инициализацию статики в том порядке, в котором они необходимы для библиотек. У меня просто есть частный статический метод Instance, как у вас выше, и я обертываю доступ к другим значениям в общедоступных статических методах доступа, которые используют этот метод Instance вместо прямых ссылок. Благодарю.
Хотя будьте осторожны при использовании потоков. Я считаю, что в GCC создание статических локальных объектов защищено от одновременного выполнения, но в Visual C ++ это не так.
Начиная с C ++ 11 и в POSIX, этоhas быть потокобезопасным.
Потрясающие! Это завершает это.
1

Это, конечно, не должно быть таким сложным, как принятый в настоящее время ответ (Дэниел Эрвикер). Класс лишний. В этом случае нет необходимости в языковой войне.

Файл .hpp:

vector<char> const & letters();

Файл .cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}
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
    };
};

Похожие вопросы