当尝试使用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
}
}
答案 0 :(得分:3)
该行:
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 :(得分: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
在课程中,地址将根据您的机器和运行而变化。