在使用Nifty Counter C ++ Idiom时,必须将构造函数调用两次吗?

时间:2012-03-14 10:10:43

标签: c++ static initialization

当尝试使用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 个答案:

答案 0 :(得分:3)

该行:

CSomeClass CDataFile::mSome;

定义CSomeClass类型的变量。这个变量的初始化分两个阶段进行:     首先它是零初始化,这(大约)意味着它所在的存储器都被设置为0。     之后,发生动态初始化。这会导致其构造函数运行。

dfinitializer遵循类似的“零初始化然后动态初始化”模式。在动态初始化步骤中,它会在operator=上调用CDataFile::mSome,以便将新的默认构建CSomeClass()分配给mSome

这一步完全没有意义,因为mSomedfinitializer的动态初始化相对于彼此是不确定的。如果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 :(得分:1)

如果在命名空间范围内定义对象,则其构造函数将为 在初始化期间的某个时刻由启动代码调用。如果你 想要使用漂亮的计数器成语,你需要以某种方式压制这个, 或者让它成为无操作。您还必须在实际中使用新的展示位置 初始化。有几种方法可以实现这一目标:

  • 我见过的大多数工业实力实施要么 在汇编程序中声明对象,或使用编译器扩展来确保 构造函数没有被调用。这不是很便携,但是 对于像iostream这样的东西,无论如何都无法在纯C ++中实现, 它通常是可以接受的。 (事实上​​,这是唯一可接受的解决方案 对于iostream对象,因为不允许它们被破坏。)

  • 我一般都安排了一个特殊的无操作构造函数 什么也没做。在形式上,它不能保证工作,但在实践中, 它确实。然后,您可以定义使用nifty的实例 使用此构造函数的计数器习惯用法。

  • 最后,如果你控制正在构建的类,那么唯一的 实例由漂亮的计数器控制,如果它可以有一个微不足道的 构造函数,你不需要在构造函数上工作 初始化初始化程序中的各个成员。

这些都不是特别好的解决方案,在新的代码中,我会使用 单身成语的一些变体。

答案 2 :(得分:-1)

是和否:是的,它被调用两次,并且在两个不同的对象上都没有被调用。

让我们说你有

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

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

因为#include A.cpp和B.cpp都有dfinitializer的本地和独立副本。

datafile.cpp依次为nifty_counter(最好用初始值为0 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

在课程中,地址将根据您的机器和运行而变化。