При использовании идиомы Nifty Counter C ++ должен ли конструктор вызываться дважды? - PullRequest
0 голосов
/ 14 марта 2012

При попытке инициализировать статические члены с помощью Nifty Counter C ++ Idiom у меня возникли некоторые проблемы с этим. Можете ли вы объяснить, как правильно использовать ideom в следующем случае?

Кажется, проблема в следующем: у меня есть два статических объекта в разных единицах компиляции, где один использует статические члены другого (CDataFile). Поскольку порядок инициализации в этом случае не определен - а в производственной среде он был в неправильном порядке - я попытался использовать идиому Nifty Counter. Но похоже, что теперь статические члены CDataFile инициализируются дважды (конструктор вызывается дважды). В первый раз конструктор вызывается в CDataFileInitializer, что нормально. После этого используются статические члены (mSome заполняется), но затем конструктор CSomeClass вызывается второй раз, а содержимое mSome очищается.

// datafile.h
class CDataFile
{
    friend class CDataFileInitializer;
protected:
    static CSomeClass mSome;
    // other code
};

static class CDataFileInitializer
{
public:
    CDataFileInitializer();
} dfinitializer;

// datafile.cpp
static int nifty_counter;
CSomeClass CDataFile::mSome; // second initialization comes from here?

CDataFileInitializer::CDataFileInitializer()
{
    if (!nifty_counter++)
    {
        printf("CDataFileInitializer Constructor\n");
        CDataFile::mSome= CSomeClass(); // first initialization
    }
}

Ответы [ 3 ]

3 голосов
/ 14 марта 2012

Линия:

CSomeClass CDataFile::mSome;

определяет переменную типа CSomeClass. Инициализация этой переменной происходит в два этапа: Сначала он инициализируется нулями, это (приблизительно) означает, что вся память, в которой он находится, установлена ​​на 0. После этого происходит динамическая инициализация. Это вызывает запуск его конструктора.

dfinitializer следует аналогичному шаблону «инициализация с нуля, а затем динамическая инициализация». На этапе динамической инициализации он вызывает operator= на CDataFile::mSome, чтобы присвоить новому построенному по умолчанию CSomeClass() значение mSome.

Этот шаг абсолютно бессмысленный, потому что динамические инициализации mSome и dfinitializer неопределенно упорядочены друг относительно друга. Если dfinialiser инициализируется первым, он попытается присвоить объекту, который не был создан (и который впоследствии будет создан по умолчанию), а если он будет инициализирован вторым, он переназначится объекту, который уже был создан.

Вместо:

CSomeClass CDataFile::mSome;

Вы должны создать область хранения, в которой может быть построен объект:

alignas(CSomeClass) unsigned char CDataFile::mSome[sizeof(CSomeClass)];

Затем измените CDataFileInitializer на:

CDataFileInitializer::CDataFileInitializer()
{
    if (!nifty_counter++)
    {
        printf("CDataFileInitializer Constructor\n");
        new (&CDataFile::mSome) CSomeClass();
    }
}

Альтернативой может быть использование статической переменной функции:

CSomeClass& getMSome() {
    static CSomeClass mSome;
    return mSome;
}

Это будет лениво инициализировать mSome потокобезопасным способом.

1 голос
/ 14 марта 2012

Если вы определяете объект в области имен, его конструктор будет вызываться кодом запуска в какой-то момент во время инициализации.Если вы хотите использовать изящную контраргументную идиому, вам нужно как-то подавить это или сделать ее недоступной.Вы также должны использовать новое размещение в фактическом инициализаторе.Есть несколько способов достичь этого:

  • Большинство реализованных мною промышленных реализаций либо объявляют объект в ассемблере, либо используют расширение компилятора, чтобы убедиться, что конструктор не 'Тебя называют.Это не очень переносимо, но для таких вещей, как iostream, которые в любом случае не могут быть реализованы на чистом C ++, это часто приемлемо.(На самом деле, это единственное приемлемое решение для объектов iostream, поскольку их нельзя разрушать.)

  • Я обычно договорился, чтобы у меня не было специальныхоп конструктор, который ничего не делает.Формально это не гарантировано, но на практике это работает.Затем вы определяете экземпляр, для которого вы используете идиому изящного счетчика, чтобы использовать этот конструктор.

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

Ни одно из этих решений не является особенно хорошим, и вновый код, я бы использовал какой-то вариант синглтоновой идиомы.

0 голосов
/ 14 марта 2012

да нет: да он вызывается дважды, а нет вызывается для двух разных объектов.

Допустим, у вас есть

// A.cpp
#include "datafile.h"
...

и

// B.cpp
#include "datafile.h"
...

из-за #include A.cpp и B.cpp будут иметь локальную и независимую копию dfinitializer.

datafile.cpp имеет в свою очередь nifty_counter (лучше определить ее с помощью начальной0 value ... static int nifty_counter = 0;) и CDatafile :: mSome (инициализируется на уровне файла).

То, что делает CDataFileInitializer ctor, назначается уже инициализированному mSome временному CSomeClass (), которыйсоздается и уничтожается на лету.

Все, что на самом деле является неправильной реализацией, делает правильные вещи только потому, что CDataFile может быть назначен.

Если проблема заключается только в инициализации статического члена данныхвсе, что вам нужно сделать, это убедиться, что что-то в модуле, содержащем определение статического члена (примечание: определение, а не объявление), вызывается другими, что вызывает некоторый побочный эффект в модуле (просто чтобы избежать оптимизации)

так что ... пустьПопробуйте лучший трюк

//some.h
#ifndef SOME_H_INCLUDED
#define SOME_H_INCLUDED

#include<iostream>
class CSome
{
public:
    CSome() { std::cout << "CSome["<<this<<"] default created" << std::endl; }
    CSome(const CSome& s) { std::cout << "CSome["<<this<<"] created from ["<<&s<<"]" << std::endl; }
    CSome& operator=(const CSome& s) { std::cout << "CSome["<<this<<"] assigned from ["<<&s<<"]" << std::endl; return *this; }
    CSome(CSome&& s) { std::cout << "CSome["<<this<<"] created moving ["<<&s<<"]" << std::endl; }
    CSome& operator=(CSome&& s) { std::cout << "CSome["<<this<<"] assigned moving ["<<&s<<"]" << std::endl; return *this; }
    ~CSome() { std::cout << "CSome["<<this<<"] destroyed" << std::endl; }
};
#endif // SOME_H_INCLUDED




//datafile.h
#ifndef DATAFILE_H_INCLUDED
#define DATAFILE_H_INCLUDED

#include "some.h"
class CDataFile
{
public:
protected:
    static CSome mSome;
};

static class CDataFileInitializer
{
public:
    CDataFileInitializer();
    ~CDataFileInitializer();
} datafileinitializer;


#endif // DATAFILE_H_INCLUDED



//datafile.cpp
#include "datafile.h"
#include <iostream>
static int nifty_counter = 0; //the one and only

CSome CDataFile::mSome; //define and initialize

CDataFileInitializer::CDataFileInitializer()
{
    std::cout << "CDataFileInitializer["<<this<<"] creation"<< std::endl;
    if(!nifty_counter++)
    {
        std::cout << "CDataFileInitializer FIRST INITIALIZATION"<< std::endl;
    }
}

CDataFileInitializer::~CDataFileInitializer()
{
    std::cout << "CDataFileInitializer["<<this<<"] destruction"<< std::endl;
    if(!--nifty_counter)
    {
        std::cout << "CDataFileInitializer LAST DESTRUCTION"<< std::endl;
    }
}


//A.cpp
#include <iostream>
static class A
{
public:
    A() { std::cout << "initializing A.cpp" << std::endl; }
    ~A() { std::cout << "cleaning A.cpp" << std::endl; }
} a;
#include "datafile.h"
// other a.cpp code ...

void call_a() { std::cout << "do something in a.ccp" << std::endl; }


//B.cpp
#include <iostream>
static class B
{
public:
    B() { std::cout << "initializing B.cpp" << std::endl; }
    ~B() { std::cout << "cleaning B.cpp" << std::endl; }
} b;
#include "datafile.h"
// other b.cpp code ...

void call_b() { std::cout << "do something in b.ccp" << std::endl; }


//main.cpp
#include <iostream>

void call_a();
void call_b();

int main()
{
    std::cout << "main" << std::endl;
    call_a();
    call_b();
    std::cout << "main return" << std::endl;
    return 0;
}

даст следующий вывод:

CDataFileInitializer[0x406035] creation
CDataFileInitializer FIRST INITIALIZATION
CSome[0x40602c] default created
initializing A.cpp
CDataFileInitializer[0x406029] creation
initializing B.cpp
CDataFileInitializer[0x406025] creation
main
do something in a.ccp
do something in b.ccp
main return
CDataFileInitializer[0x406025] destruction
cleaning B.cpp
CDataFileInitializer[0x406029] destruction
cleaning A.cpp
CSome[0x40602c] destroyed
CDataFileInitializer[0x406035] destruction
CDataFileInitializer LAST DESTRUCTION

Конечно, адреса будут меняться в зависимости от вашей машины и работы.

...